diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd4d041..6368910 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,30 +5,6 @@ on: pull_request: jobs: - build-test-docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 10.0.x - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - run: dotnet restore TypeFerence.slnx - - run: dotnet build TypeFerence.slnx --no-restore --configuration Release - - run: dotnet test TypeFerence.slnx --no-build --configuration Release - - run: dotnet src/TypeFerence.Cli/bin/Release/net10.0/typeference.dll build examples/helio --target all --out dist-ci-1 --emit-ard --publisher-domain helio.example - - run: dotnet src/TypeFerence.Cli/bin/Release/net10.0/typeference.dll build examples/helio --target all --out dist-ci-2 --emit-ard --publisher-domain helio.example - - run: diff -qr dist-ci-1 dist-ci-2 - - run: pip install -r tools/requirements-docs.txt - - run: python tools/render_whitepaper.py - - run: cp output/pdf/typeference-whitepaper.pdf /tmp/typeference-whitepaper-first.pdf - - run: python tools/render_whitepaper.py - - run: cmp /tmp/typeference-whitepaper-first.pdf output/pdf/typeference-whitepaper.pdf - - run: sudo apt-get update && sudo apt-get install -y poppler-utils - - run: pdfinfo output/pdf/typeference-whitepaper.pdf - go-implementation: runs-on: ubuntu-latest steps: @@ -44,8 +20,10 @@ jobs: working-directory: go - run: go test ./... working-directory: go - - name: Static binary (CGO_ENABLED=0) - run: CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /tmp/typeference ./cmd/typeference + - name: Static binaries (CGO_ENABLED=0) + run: | + CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /tmp/typeference ./cmd/typeference + CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /tmp/typeference-lsp ./cmd/typeference-lsp working-directory: go - name: Go binary reproduces committed reference output byte-for-byte run: /tmp/typeference diff examples/helio --against dist --emit-ard --publisher-domain helio.example @@ -56,31 +34,21 @@ jobs: GOOS=js GOARCH=wasm go build ./cmd/typeference-wasm go run ./cmd/playground-pack -root .. -out /tmp/examples.json working-directory: go - # NOTE: BETH pack/score is intentionally NOT gating CI yet. The harness is - # unproven end to end (no real judged run has been collected), and an - # offline pack+score observes nothing — a green scorecard there would be - # vacuous (see ADR-0009 and score.go's judged-coverage requirement). Wire - # it back in once a real run is exercisable in CI or on the dev machine. - # Cross-implementation conformance: both compilers run the shared fixture - # corpus in conformance/; a digest mismatch on any fixture is a broken - # build. See conformance/README.md and ADR-0005. - conformance: + # Determinism suite: the Go compiler compiles the shared fixture corpus in + # conformance/ and must reproduce the committed digests. This is the + # single-implementation successor to the cross-implementation suite; the + # fixtures and canonicalization rulings (ADR-0004) are unchanged (ADR-0014). + determinism: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 10.0.x - uses: actions/setup-go@v5 with: go-version: stable cache-dependency-path: go/go.sum - - name: Conformance suite (Go implementation) - run: go test ./conformance -v + - run: go test ./conformance -v working-directory: go - - name: Conformance suite (C# reference implementation) - run: dotnet test TypeFerence.slnx --filter FullyQualifiedName~ConformanceSuiteTests # The repository-root AGENTS.md and dist-maintainer are build artifacts of # agents/maintainer (the self-hosted maintainer definition). Drift between @@ -98,3 +66,18 @@ jobs: working-directory: go - name: Root AGENTS.md is the compiled artifact run: cmp AGENTS.md dist-maintainer/neutral/typeference-maintainer/AGENTS.md + + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install -r tools/requirements-docs.txt + - run: python tools/render_whitepaper.py + - run: cp output/pdf/typeference-whitepaper.pdf /tmp/typeference-whitepaper-first.pdf + - run: python tools/render_whitepaper.py + - run: cmp /tmp/typeference-whitepaper-first.pdf output/pdf/typeference-whitepaper.pdf + - run: sudo apt-get update && sudo apt-get install -y poppler-utils + - run: pdfinfo output/pdf/typeference-whitepaper.pdf diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3131db8..0a4d1e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,20 +15,16 @@ permissions: contents: write jobs: - # Nothing ships unless both implementations, the shared conformance suite, - # and the self-host drift gate are green at the tagged commit. + # Nothing ships unless the test suite, the determinism suite, and the + # self-host drift gate are green at the tagged commit. verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 10.0.x - uses: actions/setup-go@v5 with: go-version: stable cache-dependency-path: go/go.sum - - run: dotnet test TypeFerence.slnx - run: go test ./... working-directory: go - name: Self-host drift gate diff --git a/.gitignore b/.gitignore index 2e3fde8..9a70256 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,8 @@ __pycache__/ web/playground/typeference.wasm web/playground/wasm_exec.js web/playground/examples.json + +# Codex remote attachment scratch +.codex-remote-attachments/ +go/typeference-wasm +*.wasm diff --git a/AGENTS.md b/AGENTS.md index 44fb18d..5b68a87 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ The agent that maintains the TypeFerence repository, defined in TypeFerence's ow - Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture. - Every canonicalization or composition ruling ships with a fixture under conformance/fixtures in the same change. - Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not. -- Any change to code generation must keep the C# and Go implementations byte-identical, verified by the conformance suite before merge. +- Any change to code generation must reproduce the committed digests, verified by the determinism suite before merge. - Never weaken determinism, provenance, or fail-closed behavior to make a change easier. - A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest. - Repeated builds from identical source must stay byte-identical on every platform. @@ -16,7 +16,7 @@ The agent that maintains the TypeFerence repository, defined in TypeFerence's ow - signatureIntent.required fails closed; the unsigned-staging option exists solely to emit payloads for an external signer. - TypeFerence imports externally produced signatures; it never signs, never verifies cryptographic validity, and never resolves keys. - Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation. -- Every commit builds and passes both implementations' test suites and the shared conformance suite. +- Every commit builds and passes the test suite and the determinism suite. - Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges. - Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements. - Commit messages are conventional and written for a critical human reader. @@ -28,5 +28,5 @@ The agent that maintains the TypeFerence repository, defined in TypeFerence's ow ## Available skills - `typeference-maintainer.audit-drift`: Confirms the committed AGENTS.md and maintainer bundle are exact build artifacts of this definition. -- `typeference-maintainer.verify-conformance`: Runs the shared conformance suite on both implementations and reports any digest disagreement. +- `typeference-maintainer.verify-conformance`: Runs the determinism suite and reports whether the compiler reproduces the committed digests. diff --git a/CHANGELOG.md b/CHANGELOG.md index 84e32f3..fd46c45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,28 @@ versioning. Tool versions (this file) are independent of the typed-resource TypeFerence is an experimental reference implementation; pre-1.0 versions make no compatibility promises between minor versions. +## [Unreleased] + +### Changed + +- **Go-only implementation** ([ADR-0014](docs/decisions/0014-go-only-implementation.md)). + The C# reference implementation was retired; the Go implementation is now the + sole implementation. The specification stays normative in principle. The + cross-implementation conformance suite becomes a single-implementation + golden-file determinism suite over the same fixtures — the determinism + guarantee is unchanged. + +### Added + +- **`.tfer` source format** and object-model constructs (ADRs 0012–0019): + invocation-mode skill variants, user-defined typed context (`contextType` + refinement, context held by id, `requiresContextTypes`), tools as extern + declarations (`requiresTools`), capability exposure/visibility, and sealing + (`sealed`/`required` bindings). All additive and opt-in. +- **Language server** (`typeference-lsp`): authoring and composition + diagnostics, completion, go-to-definition, and document symbols for `.tfer` + and `.yaml` sources. + ## [0.0.4] - 2026-07-15 ### Added diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index 1a31f06..0000000 --- a/Directory.Build.props +++ /dev/null @@ -1,11 +0,0 @@ - - - net10.0 - enable - enable - latest - true - true - 0.0.4 - - diff --git a/Makefile b/Makefile index 8b385df..5ffdf1f 100644 --- a/Makefile +++ b/Makefile @@ -1,41 +1,37 @@ -# TypeFerence build entry points. Requires: Go 1.24+, .NET 10 SDK (reference -# implementation only). All artifacts are deterministic; run `make conformance` -# to verify both implementations agree byte-for-byte. +# TypeFerence build entry points. Requires: Go 1.24+ and nothing else. All +# artifacts are deterministic; run `make conformance` to verify the compiler +# reproduces the committed digests byte-for-byte (ADR-0014). GO ?= go -DOTNET ?= dotnet VERSION ?= dev GOFLAGS := -trimpath LDFLAGS := -s -w -X main.version=$(VERSION) BINDIR := bin -.PHONY: all build build-go build-dotnet test test-go test-dotnet conformance \ +.PHONY: all build build-go build-lsp test test-go conformance \ selfhost selfhost-check fmt vet clean release-binaries playground all: build test -build: build-go build-dotnet +build: build-go build-lsp # Single static binary, no runtime dependencies. build-go: cd go && CGO_ENABLED=0 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o ../$(BINDIR)/typeference$(shell $(GO) env GOEXE) ./cmd/typeference -build-dotnet: - $(DOTNET) build TypeFerence.slnx +# Language server for .tfer/.yaml authoring diagnostics (internal/lsp). +build-lsp: + cd go && CGO_ENABLED=0 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o ../$(BINDIR)/typeference-lsp$(shell $(GO) env GOEXE) ./cmd/typeference-lsp -test: test-go test-dotnet +test: test-go test-go: cd go && $(GO) test ./... -test-dotnet: - $(DOTNET) test TypeFerence.slnx - -# Cross-implementation conformance: both compilers run the shared fixture -# corpus; digests must match the manifests and each other. +# Determinism suite: the compiler compiles the shared fixture corpus and must +# reproduce the committed digests (ADR-0014). conformance: cd go && $(GO) test ./conformance -run TestConformance -v - $(DOTNET) test TypeFerence.slnx --filter FullyQualifiedName~ConformanceSuiteTests # Recompile the self-hosted maintainer definition (agents/maintainer) into its # committed artifacts: dist-maintainer and the repository-root AGENTS.md. @@ -50,7 +46,6 @@ selfhost-check: build-go fmt: cd go && gofmt -l -w . - $(DOTNET) format TypeFerence.slnx vet: cd go && $(GO) vet ./... diff --git a/README.md b/README.md index eb59ce0..28f508b 100644 --- a/README.md +++ b/README.md @@ -137,33 +137,22 @@ what "No differences." means. `eval` and `equivalence` are covered in [Behavioral evals](#behavioral-evals) below; both default to a dry run that never makes a network call. -## Two implementations, one specification - -The C# implementation under `src/` is the reference implementation; the Go -implementation under `go/` is an independent second implementation. Both compile -identical source to **byte-identical artifacts**, enforced by a shared conformance -suite ([conformance/](conformance/README.md)) that runs on both implementations in -CI — expected digests are generated by one implementation and independently -verified by the other. That suite is what makes the -[specification](docs/specification.md) real rather than aspirational. - -### Reference implementation (C#) - -Requires the .NET 10 SDK. - -```powershell -dotnet build TypeFerence.slnx -dotnet src/TypeFerence.Cli/bin/Debug/net10.0/typeference.dll validate examples/helio -dotnet src/TypeFerence.Cli/bin/Debug/net10.0/typeference.dll serve dist/neutral -``` - -The MCP server (`typeference serve`) is currently implemented in the reference -implementation only. It exposes tools such as: - -- `executive-assistant.prepare-brief` -- `payments-repo-agent.repository-status` - -Calls return a deterministic invocation package for the host agent. TypeFerence does not embed an LLM provider. +## One implementation, one specification + +The Go implementation under `go/` is the reference implementation. The +[specification](docs/specification.md) stays normative in principle — the +abstraction is the contract, and anyone may realize it differently — but this +repository's Go compiler is the one living answer ([ADR-0014](docs/decisions/0014-go-only-implementation.md)). +Determinism is preserved and made visible by the +[conformance suite](conformance/README.md): the compiler must reproduce the +committed digests byte-for-byte. + +An earlier C# reference implementation was retired when the project committed to +being a tool rather than a multi-implementation standard. Determinism — the +property that makes `diff` and the committed-digest guarantee real — is a property +of one good compiler and was unaffected; only the second implementation, and the +cross-implementation half of the conformance suite, went away. TypeFerence embeds +no LLM provider and holds no deployment state. ## Self-hosting @@ -220,8 +209,7 @@ TypeFerence does not hold signing keys. An external signer can produce detached ## Repository map -- `src/` - C# reference implementation: compiler, target adapters, CLI, and MCP runtime. -- `go/` - independent Go implementation: compiler, target adapters, CLI, and eval harness. +- `go/` - the Go implementation: compiler, target adapters, CLI, language server (`cmd/typeference-lsp`), and eval harness. - `conformance/` - shared cross-implementation conformance fixtures (byte-identity contract). - `examples/helio/` - fictional cross-domain organization. - `web/playground/` - browser playground: the Go compiler built for `js/wasm`, plus the BETH operator console. @@ -230,7 +218,6 @@ TypeFerence does not hold signing keys. An external signer can produce detached - `docs/specification.md` - normative v3 behavior. - `docs/decisions/` - architecture decision records. - `docs/whitepaper.md` and `output/pdf/typeference-whitepaper.pdf` - design paper. -- `tests/` - C# test suite: type system, targets, determinism, conformance runner, MCP integration. - `CHANGELOG.md` and `docs/release-checklist.md` - versioning and release process. ## Design boundaries diff --git a/TypeFerence.slnx b/TypeFerence.slnx deleted file mode 100644 index 078e4a6..0000000 --- a/TypeFerence.slnx +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/agents/maintainer/agents/typeference-maintainer.agent.yaml b/agents/maintainer/agents/typeference-maintainer.agent.tfer similarity index 98% rename from agents/maintainer/agents/typeference-maintainer.agent.yaml rename to agents/maintainer/agents/typeference-maintainer.agent.tfer index bbeee3d..e1e870a 100644 --- a/agents/maintainer/agents/typeference-maintainer.agent.yaml +++ b/agents/maintainer/agents/typeference-maintainer.agent.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: agent id: typeference/typeference-maintainer@0.1.0 @@ -12,3 +13,4 @@ slots: repositoryMap: context/repository-map.md contextFiles: - context/repository-map.md +--- diff --git a/agents/maintainer/capabilities/audit-drift.capability.yaml b/agents/maintainer/capabilities/audit-drift.capability.tfer similarity index 98% rename from agents/maintainer/capabilities/audit-drift.capability.yaml rename to agents/maintainer/capabilities/audit-drift.capability.tfer index 0f60f00..376dee4 100644 --- a/agents/maintainer/capabilities/audit-drift.capability.yaml +++ b/agents/maintainer/capabilities/audit-drift.capability.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: capability id: typeference/capabilities/audit-drift@0.1.0 @@ -5,3 +6,4 @@ displayName: Audit Self-Host Drift description: Recompile the maintainer definition and report drift against the committed artifacts. inputSchema: '{"type":"object","additionalProperties":false}' outputSchema: '{"type":"object","properties":{"clean":{"type":"boolean"},"driftedFiles":{"type":"array","items":{"type":"string"}}},"required":["clean","driftedFiles"]}' +--- diff --git a/agents/maintainer/capabilities/verify-conformance.capability.yaml b/agents/maintainer/capabilities/verify-conformance.capability.tfer similarity index 75% rename from agents/maintainer/capabilities/verify-conformance.capability.yaml rename to agents/maintainer/capabilities/verify-conformance.capability.tfer index b1489e2..eac6ed5 100644 --- a/agents/maintainer/capabilities/verify-conformance.capability.yaml +++ b/agents/maintainer/capabilities/verify-conformance.capability.tfer @@ -1,7 +1,9 @@ +--- schemaVersion: 3 kind: capability id: typeference/capabilities/verify-conformance@0.1.0 displayName: Verify Conformance -description: Run both implementations against the shared conformance suite and report agreement. +description: Run the determinism suite and report whether the compiler reproduces the committed digests. inputSchema: '{"type":"object","additionalProperties":false}' outputSchema: '{"type":"object","properties":{"passed":{"type":"boolean"},"mismatches":{"type":"array","items":{"type":"string"}}},"required":["passed","mismatches"]}' +--- diff --git a/agents/maintainer/context/contribution.md b/agents/maintainer/context/contribution.md index 3d03d8b..49486ac 100644 --- a/agents/maintainer/context/contribution.md +++ b/agents/maintainer/context/contribution.md @@ -2,9 +2,8 @@ - Work on feature branches; never rewrite published history; never tag or publish a release outside the checklist in `docs/release-checklist.md`. -- Before any commit: `dotnet test TypeFerence.slnx`, `go test ./...` (from `go/`), - and the conformance suite (`make conformance`) all pass. A commit that breaks any - of them does not land. +- Before any commit: `go test ./...` (from `go/`) and the determinism suite + (`make conformance`) both pass. A commit that breaks either does not land. - Design decisions with real tradeoffs — spec semantics, canonical bytes, trust model, dependencies — are recorded in `docs/decisions/` as numbered ADRs in the same change. diff --git a/agents/maintainer/context/determinism.md b/agents/maintainer/context/determinism.md index 87c35d1..384b130 100644 --- a/agents/maintainer/context/determinism.md +++ b/agents/maintainer/context/determinism.md @@ -1,19 +1,20 @@ # Determinism guarantees -Identical source must compile to identical bytes — across repeated builds, across -platforms, and across implementations. The guarantee is enforced, not aspirational: +Identical source must compile to identical bytes — across repeated builds and +across platforms. The guarantee is enforced, not aspirational: - `conformance/fixtures/` records the expected `typeference-directory-v1` digest of - every emitted target for 25+ fixtures. Both implementations run the corpus in CI. + every emitted target for the fixture corpus; the compiler must reproduce them in + CI (a golden-file determinism suite). - The committed `dist/` tree is the fully materialized reference output for - `examples/helio`; both implementations byte-compare against it in their own tests. + `examples/helio`; the compiler byte-compares against it in its tests. - The committed root `AGENTS.md` and `dist-maintainer/` are build artifacts of `agents/maintainer/`; CI recompiles the definition and fails on any drift. Rules that protect the guarantee: -- Digest values are regenerated (`go test ./conformance -update`, then verified by - the C# runner), never typed by hand. +- Digest values are regenerated (`go test ./conformance -update`), never typed by + hand. - Canonical serialization is defined in `docs/specification.md` ("Deterministic compilation"); any change to it is a specification change with an ADR. - Nothing about determinism, provenance, or fail-closed behavior is ever relaxed to diff --git a/agents/maintainer/context/repository-map.md b/agents/maintainer/context/repository-map.md index 34b78dd..97045b1 100644 --- a/agents/maintainer/context/repository-map.md +++ b/agents/maintainer/context/repository-map.md @@ -5,15 +5,15 @@ | `docs/specification.md` | Normative specification (source of truth). | | `docs/whitepaper.md` | Motivation and design narrative. | | `docs/decisions/` | Architecture decision records. | -| `src/` | C# reference implementation (.NET 10). | -| `go/` | Go implementation (static binary; module `github.com/buchk/TypeFerence/go`). | -| `tests/` | C# test suite, including the conformance runner. | -| `go/conformance/` | Go conformance runner (`-update` regenerates digests). | -| `conformance/` | Shared cross-implementation fixture corpus. | +| `go/` | The implementation (static binary; module `github.com/buchk/TypeFerence/go`). | +| `go/cmd/typeference-lsp/` | Language server for `.tfer`/`.yaml` authoring. | +| `editors/vscode/` | VS Code client for the language server. | +| `go/conformance/` | Determinism runner (`-update` regenerates digests). | +| `conformance/` | Golden-file fixture corpus. | | `examples/helio` | Example organization used by tests and the quick start. | | `dist/` | Committed reference output of `examples/helio` (byte-compared in CI). | | `agents/maintainer/` | This definition; compiled into the root `AGENTS.md` and `dist-maintainer/`. | -| `.github/workflows/ci.yml` | Build, test, conformance, and self-host drift gates. | +| `.github/workflows/ci.yml` | Build, test, determinism, and self-host drift gates. | The root `AGENTS.md` is generated from this definition. To change it, edit the resources under `agents/maintainer/` and run `make selfhost`; never edit the diff --git a/agents/maintainer/context/spec-workflow.md b/agents/maintainer/context/spec-workflow.md index 2d6f48b..2ae7e78 100644 --- a/agents/maintainer/context/spec-workflow.md +++ b/agents/maintainer/context/spec-workflow.md @@ -1,10 +1,9 @@ # Specification-first workflow The normative specification is `docs/specification.md` at the repository root. It is -the source of truth for both implementations: - -- C# reference implementation: `src/TypeFerence.Core`, `src/TypeFerence.Cli` -- Go implementation: `go/` +the source of truth; the Go implementation under `go/` is its reference realization, +and the spec stays normative in principle so another implementation could be built +against it (ADR-0014). A change is *semantic* when it alters what source trees are valid, how composition resolves, or what bytes compilation emits. Semantic changes follow this order, in one @@ -13,9 +12,8 @@ reviewable change set: 1. Amend `docs/specification.md`. 2. Record the decision and rejected alternatives as an ADR in `docs/decisions/`. 3. Add or update fixtures in `conformance/fixtures/` capturing the ruling. -4. Update both implementations until the conformance suite passes on both. +4. Update the implementation until the determinism suite passes. -If an implementation is found to disagree with the specification, the specification -wins. If the specification is ambiguous enough that two honest implementations could -diverge, that is a specification bug: fix the text, do not encode a private -interpretation in code. +If the implementation is found to disagree with the specification, the specification +wins. If the specification is ambiguous, that is a specification bug: fix the text, +do not encode a private interpretation in code. diff --git a/agents/maintainer/context/trust-model.md b/agents/maintainer/context/trust-model.md index 2569e47..c9b74ee 100644 --- a/agents/maintainer/context/trust-model.md +++ b/agents/maintainer/context/trust-model.md @@ -6,8 +6,8 @@ signatures"). The invariants that must survive every change: - **The signature map lives outside the source root.** The source-package digest covers every file under the source root; a signature stored inside would change - the digest embedded in the very payload being signed. Both implementations reject - a signature map path beneath the source root. + the digest embedded in the very payload being signed. The compiler rejects a + signature map path beneath the source root. - **Fail-closed stays fail-closed.** When `signatureIntent.required` is true and no signature is imported, publication fails. `--allow-unsigned-trust` exists solely to emit the payload for an external signer, and its output is visibly unsigned. diff --git a/agents/maintainer/profiles/contribution-workflow.profile.yaml b/agents/maintainer/profiles/contribution-workflow.profile.tfer similarity index 84% rename from agents/maintainer/profiles/contribution-workflow.profile.yaml rename to agents/maintainer/profiles/contribution-workflow.profile.tfer index 088c2d4..c0a09be 100644 --- a/agents/maintainer/profiles/contribution-workflow.profile.yaml +++ b/agents/maintainer/profiles/contribution-workflow.profile.tfer @@ -1,12 +1,14 @@ +--- schemaVersion: 3 kind: profile id: typeference/profiles/contribution-workflow@0.1.0 displayName: Contribution Workflow description: How changes to this repository are made, reviewed, and recorded. workingNorms: - - Every commit builds and passes both implementations' test suites and the shared conformance suite. + - Every commit builds and passes the test suite and the determinism suite. - Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges. - Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements. - Commit messages are conventional and written for a critical human reader. contextFiles: - context/contribution.md +--- diff --git a/agents/maintainer/profiles/determinism-guardian.profile.yaml b/agents/maintainer/profiles/determinism-guardian.profile.tfer similarity index 79% rename from agents/maintainer/profiles/determinism-guardian.profile.yaml rename to agents/maintainer/profiles/determinism-guardian.profile.tfer index ae6b007..c4b55ca 100644 --- a/agents/maintainer/profiles/determinism-guardian.profile.yaml +++ b/agents/maintainer/profiles/determinism-guardian.profile.tfer @@ -1,10 +1,11 @@ +--- schemaVersion: 3 kind: profile id: typeference/profiles/determinism-guardian@0.1.0 displayName: Determinism Guardian -description: Any codegen change must preserve byte-identical output across both implementations. +description: Any codegen change must preserve byte-identical output, verified by the determinism suite. workingNorms: - - Any change to code generation must keep the C# and Go implementations byte-identical, verified by the conformance suite before merge. + - Any change to code generation must reproduce the committed digests, verified by the determinism suite before merge. - Never weaken determinism, provenance, or fail-closed behavior to make a change easier. - A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest. - Repeated builds from identical source must stay byte-identical on every platform. @@ -13,3 +14,4 @@ contextFiles: skills: - ref: typeference/skills/verify-conformance@0.1.0 - ref: typeference/skills/audit-drift@0.1.0 +--- diff --git a/agents/maintainer/profiles/spec-conformance.profile.yaml b/agents/maintainer/profiles/spec-conformance.profile.tfer similarity index 92% rename from agents/maintainer/profiles/spec-conformance.profile.yaml rename to agents/maintainer/profiles/spec-conformance.profile.tfer index b3dfc3d..ac4cd14 100644 --- a/agents/maintainer/profiles/spec-conformance.profile.yaml +++ b/agents/maintainer/profiles/spec-conformance.profile.tfer @@ -1,8 +1,9 @@ +--- schemaVersion: 3 kind: profile id: typeference/profiles/spec-conformance@0.1.0 displayName: Spec Conformance -description: Semantic changes land in the specification first, then in both implementations, with conformance fixtures. +description: Semantic changes land in the specification first, then in the implementation, with conformance fixtures. workingNorms: - Semantic changes land in docs/specification.md before either implementation changes behavior. - Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture. @@ -10,3 +11,4 @@ workingNorms: - Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not. contextFiles: - context/spec-workflow.md +--- diff --git a/agents/maintainer/profiles/trust-signing.profile.yaml b/agents/maintainer/profiles/trust-signing.profile.tfer similarity index 98% rename from agents/maintainer/profiles/trust-signing.profile.yaml rename to agents/maintainer/profiles/trust-signing.profile.tfer index 0b0272d..7329ad4 100644 --- a/agents/maintainer/profiles/trust-signing.profile.yaml +++ b/agents/maintainer/profiles/trust-signing.profile.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: profile id: typeference/profiles/trust-signing@0.1.0 @@ -10,3 +11,4 @@ workingNorms: - Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation. contextFiles: - context/trust-model.md +--- diff --git a/agents/maintainer/skills/audit-drift.skill.yaml b/agents/maintainer/skills/audit-drift.skill.tfer similarity index 99% rename from agents/maintainer/skills/audit-drift.skill.yaml rename to agents/maintainer/skills/audit-drift.skill.tfer index e8bfaee..411edc6 100644 --- a/agents/maintainer/skills/audit-drift.skill.yaml +++ b/agents/maintainer/skills/audit-drift.skill.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: skill id: typeference/skills/audit-drift@0.1.0 @@ -17,3 +18,4 @@ contextFiles: - context/determinism.md inputSchema: '{"type":"object","additionalProperties":false}' outputSchema: '{"type":"object","properties":{"clean":{"type":"boolean"},"driftedFiles":{"type":"array","items":{"type":"string"}}},"required":["clean","driftedFiles"]}' +--- diff --git a/agents/maintainer/skills/verify-conformance.skill.tfer b/agents/maintainer/skills/verify-conformance.skill.tfer new file mode 100644 index 0000000..c5d85c7 --- /dev/null +++ b/agents/maintainer/skills/verify-conformance.skill.tfer @@ -0,0 +1,18 @@ +--- +schemaVersion: 3 +kind: skill +id: typeference/skills/verify-conformance@0.1.0 +binds: typeference/capabilities/verify-conformance@0.1.0 +displayName: Verify Conformance Skill +description: Runs the determinism suite and reports whether the compiler reproduces the committed digests. +instructions: | + Run `go test ./conformance` from the go/ directory (or `make conformance`). + Report passed=true only when every fixture reproduces its committed digest. + List each failing fixture and target as a mismatch. Never resolve a mismatch + by editing a digest; find the compiler regression, or take the ruling to the + specification with an ADR and a regenerated fixture. +contextFiles: + - context/determinism.md +inputSchema: '{"type":"object","additionalProperties":false}' +outputSchema: '{"type":"object","properties":{"passed":{"type":"boolean"},"mismatches":{"type":"array","items":{"type":"string"}}},"required":["passed","mismatches"]}' +--- diff --git a/agents/maintainer/skills/verify-conformance.skill.yaml b/agents/maintainer/skills/verify-conformance.skill.yaml deleted file mode 100644 index 7cec76b..0000000 --- a/agents/maintainer/skills/verify-conformance.skill.yaml +++ /dev/null @@ -1,18 +0,0 @@ -schemaVersion: 3 -kind: skill -id: typeference/skills/verify-conformance@0.1.0 -binds: typeference/capabilities/verify-conformance@0.1.0 -displayName: Verify Conformance Skill -description: Runs the shared conformance suite on both implementations and reports any digest disagreement. -instructions: | - Run `go test ./conformance` from the go/ directory, then run - `dotnet test TypeFerence.slnx --filter FullyQualifiedName~ConformanceSuiteTests` - from the repository root (or `make conformance` for both). Report passed=true - only when every fixture passes on both implementations. List each failing - fixture and target as a mismatch. Never resolve a mismatch by editing a - digest; find the diverging implementation or take the ruling to the - specification with an ADR and a new fixture. -contextFiles: - - context/determinism.md -inputSchema: '{"type":"object","additionalProperties":false}' -outputSchema: '{"type":"object","properties":{"passed":{"type":"boolean"},"mismatches":{"type":"array","items":{"type":"string"}}},"required":["passed","mismatches"]}' diff --git a/conformance/README.md b/conformance/README.md index 154df22..b017d7d 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -1,9 +1,11 @@ -# TypeFerence conformance suite +# TypeFerence determinism suite -Language-neutral fixtures that every conforming implementation must compile to -byte-identical artifacts. This suite is what makes the specification real: the C# -reference implementation and the Go implementation both run it in CI, and a digest -mismatch on any fixture is a broken build. +Language-neutral fixtures the compiler must reproduce as byte-identical +artifacts. This suite pins the specification's canonical output: the Go +implementation runs it in CI, and a digest mismatch on any fixture is a broken +build. The fixtures and canonicalization rulings (ADR-0004) are the same corpus +that once verified two implementations against each other; a single +implementation now carries it forward as a golden-file guarantee (ADR-0014). ## Layout @@ -29,22 +31,16 @@ All fixtures build with `--target all`. ## Running -- Go implementation: `cd go && go test ./conformance` -- Reference implementation: `dotnet test --filter FullyQualifiedName~ConformanceSuiteTests` -- Both: `make conformance` +`cd go && go test ./conformance` (or `make conformance`). ## Updating expectations -Digests are generated by the Go runner and independently verified by the C# runner — -a manifest can only be green when both implementations agree: - ``` cd go && go test ./conformance -run TestConformance -update -dotnet test TypeFerence.slnx --filter FullyQualifiedName~ConformanceSuiteTests ``` Never hand-edit a digest. If a digest changes, either the spec changed (requires an -ADR) or an implementation broke. +ADR) or the compiler broke. ## Byte fidelity diff --git a/conformance/fixtures/006-capability-promotion/manifest.json b/conformance/fixtures/006-capability-promotion/manifest.json index 98202ab..1a184c6 100644 --- a/conformance/fixtures/006-capability-promotion/manifest.json +++ b/conformance/fixtures/006-capability-promotion/manifest.json @@ -3,8 +3,8 @@ "expect": "success", "digests": { "codex": "sha256:1609387a77916ac4cc0d0ba40959e3d0ab52214fa7577706037b578c8730e176", - "copilot": "sha256:de256b844ada436fdfd7f8bc66a82a4931614a66c6280080057287aaf2000ec9", - "cursor": "sha256:d1ac17dd7361af178e6f9bad31aa401207ea883058e400a8765c0deacbcaf32c", + "copilot": "sha256:7fe35251ceda7e9543751a5cfa9d4f9d33b182f5c494b44a4b4ecb4febebadcf", + "cursor": "sha256:87376f51596c16d788328cd9e2663d57501e8d39b56862050262ee407447df62", "neutral": "sha256:7b610852e3beb112d8e0c84d4683362de8ab1893a174c58b074fd5d39bf5137f" } } diff --git a/conformance/fixtures/007-capability-override/manifest.json b/conformance/fixtures/007-capability-override/manifest.json index a7a3625..5b19f02 100644 --- a/conformance/fixtures/007-capability-override/manifest.json +++ b/conformance/fixtures/007-capability-override/manifest.json @@ -3,8 +3,8 @@ "expect": "success", "digests": { "codex": "sha256:46ccd5a077279895ebbf3fc0abefda0eb7185cbda465a2b89c085b3e78b0cae6", - "copilot": "sha256:eedeb711a5672f38cf3d44a38eeb0fd7916ed296aec148555007884accd0c9b8", - "cursor": "sha256:594544ea57631c99f7bf5a5a72b1d20a1f55720c5ba221d6bcb82a5f4a3ad95f", + "copilot": "sha256:51dce2817ea87a13fb185bcc8e1774da146a1d8a313ce3798e94c6464538b626", + "cursor": "sha256:6e579f2f99d7804938d71d187c787acb37144c53810940bebf13505d574dbb28", "neutral": "sha256:6683382bbd35ec2eee7919cebdae379f403e27e860be18ec0e175202840f7536" } } diff --git a/conformance/fixtures/009-interface-embedding/manifest.json b/conformance/fixtures/009-interface-embedding/manifest.json index cfa7998..7f419f2 100644 --- a/conformance/fixtures/009-interface-embedding/manifest.json +++ b/conformance/fixtures/009-interface-embedding/manifest.json @@ -3,8 +3,8 @@ "expect": "success", "digests": { "codex": "sha256:696950759cf1292674604ef9aba90bde3d6b00fa57c6da93c7c40fef860c8453", - "copilot": "sha256:57134957f13a8e428f674b1f27c8d6a1ba9634209641b89382bbec53f126ab16", - "cursor": "sha256:e741736e62b509556be1b0bbb90221eb38e23efb7d0074788a3dbe14295b0c2d", + "copilot": "sha256:48c1d3b17f64c587e88b775e02dc009b08eb372a7cc6083f5fd54813b7b777c6", + "cursor": "sha256:834f7d4da4c7185b75bc2755cee227684b4d57ffb5e1e4e6cd00eb28c5be1545", "neutral": "sha256:f649f40e03ab11569f51f1b8a781350e42f769027e306018380dc2d68a29fc70" } } diff --git a/conformance/fixtures/010-unicode-content/manifest.json b/conformance/fixtures/010-unicode-content/manifest.json index 9570ab2..c2dce0d 100644 --- a/conformance/fixtures/010-unicode-content/manifest.json +++ b/conformance/fixtures/010-unicode-content/manifest.json @@ -3,10 +3,10 @@ "expect": "success", "emitArd": "conformance.example", "digests": { - "ard": "sha256:f1190bbcd05789e94f7bf2c05a2c44796cc7ab51cc5169a3da47158a58327c73", + "ard": "sha256:8a833d348bf81ed7523357aef3f95e5e93da2a7b569402a0750676e426c5531c", "codex": "sha256:7dce90627866e66ba3d8dc3256748e7e4725d7142ab33761c7d41e14957b01a0", - "copilot": "sha256:a53323547d183be82fe7e12e8b1053a147ca4035dad589443a443e03731b466c", - "cursor": "sha256:fe0a19e38eab9b2b2a0eefbaa9b06648a1a79e72d5e9e2a238ce9f5a5ad15331", + "copilot": "sha256:12ad8cc4400746f81b39554457170dc88e7e4a326c3a51b13d116e78fc14c0da", + "cursor": "sha256:709561c48dc999bfc2f0088af663ce459c7e3c8b1f09688e583d98f24de39241", "neutral": "sha256:f2090385d737e4ad96dec563f30fcd04452a6b5a28b217a431c39d8568cb70f3" } } diff --git a/conformance/fixtures/015-kitchen-sink/manifest.json b/conformance/fixtures/015-kitchen-sink/manifest.json index e0e7257..f6ff8b3 100644 --- a/conformance/fixtures/015-kitchen-sink/manifest.json +++ b/conformance/fixtures/015-kitchen-sink/manifest.json @@ -3,10 +3,10 @@ "expect": "success", "emitArd": "conformance.example", "digests": { - "ard": "sha256:ec9641e8ba188bdc513a8058559204fe8f175047b2fb2e0da897f1193ec49f7b", + "ard": "sha256:154e0aae1a3e97afaa75c42a828651c11ecde5fe83e1ddf40a96916cbe5e83d4", "codex": "sha256:e7cbf898886493c2732b140f46dd43ac1100ae4d3a1dcee2f49085eeea5b76f2", - "copilot": "sha256:c562cf680d46f02891fb4679887ab508de751ed03bcd70ff9b3b11d405587087", - "cursor": "sha256:627fc2d6d7349e5c9c2d556f23cb41f89685fbb46268c64c453b8639e76f992b", + "copilot": "sha256:4564243c55a037c3ed543cd7e16a343dc2ed9957cffabaa3a018b1705effa7f1", + "cursor": "sha256:33c54fbdc03a501e2b7a9d125afb3b378ea54af9e4bf9ffee073eaa2d4379b8a", "neutral": "sha256:3299639f297a9b6b6ac3bc27fb12febe47ac30aee53c85e278702c876177b222" } } diff --git a/conformance/fixtures/024-number-token-preservation/manifest.json b/conformance/fixtures/024-number-token-preservation/manifest.json index a80cff2..df68b8d 100644 --- a/conformance/fixtures/024-number-token-preservation/manifest.json +++ b/conformance/fixtures/024-number-token-preservation/manifest.json @@ -3,8 +3,8 @@ "expect": "success", "digests": { "codex": "sha256:4e68ed1620e1cf4dcb498e1f2b5ad4d8cceb650d96b831f94e948eaa7068365e", - "copilot": "sha256:f11ea44b8f7763ffb2534cacbe3360bf2e80ad61cc68ebd5571d9d11b4d19fd2", - "cursor": "sha256:fceab32b26b09577e5e2a08f557a26701c5175c2fdbf5e4e1c8d2ec3b2826b6d", + "copilot": "sha256:30b951e22842352d4a2b4a52eb6eba5df1e49cb4acd4f6df3a5468b81090bba3", + "cursor": "sha256:7c6895913f6454435161e20bf02f7b93738947c4bc1d4db32224153316396578", "neutral": "sha256:3bd8e2d7d8181cab633f36500d71d120043a9eebd37989bd93db27c9a9c27a51" } } diff --git a/conformance/fixtures/025-promoted-skill-context/manifest.json b/conformance/fixtures/025-promoted-skill-context/manifest.json index c813cc4..76576b4 100644 --- a/conformance/fixtures/025-promoted-skill-context/manifest.json +++ b/conformance/fixtures/025-promoted-skill-context/manifest.json @@ -3,8 +3,8 @@ "expect": "success", "digests": { "codex": "sha256:aadc135137def478e6661a47eaabb840edad266a2cc55b1c5f58f57ef7ed357a", - "copilot": "sha256:9f9a77344aa6d67b0120e9b7b52ca2ea15935d59868a92228f7f2524e3d3986e", - "cursor": "sha256:9abe4d49483031e3be6fee8d992221abc89b875d3d46a63f8a8c77dbaa40f686", + "copilot": "sha256:0fb0e5507a3ba9e80665b30fbb6a2211e967ef2bb01a246ed4b7041a5fdc5dca", + "cursor": "sha256:c39aaed1b48917075aab9a4aa35894cf6840e770b82ba65a493e60c820bcbcf3", "neutral": "sha256:61f9c3147c940916bdd142b5709c13ffccd8f0b9865e5156bea9577d50eda8a0" } } diff --git a/dist-maintainer/ard/ai-catalog.json b/dist-maintainer/ard/ai-catalog.json index 7272ade..71ecca5 100644 --- a/dist-maintainer/ard/ai-catalog.json +++ b/dist-maintainer/ard/ai-catalog.json @@ -13,77 +13,77 @@ "version": "1.0.0", "data": { "schemaVersion": 1, - "digest": "sha256:c42776752aef743bd24749eca2f06b0da9b27d51b9edfce13984e39e0aa565c2", + "digest": "sha256:375d7da2bd1b2d11043af257e1e8af1047395592726bba7c6bcc601d72351908", "files": [ { - "path": "agents/typeference-maintainer.agent.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: agent\nid: typeference/typeference-maintainer@0.1.0\ndisplayName: TypeFerence Maintainer\ndescription: The agent that maintains the TypeFerence repository, defined in TypeFerence\u0027s own terms and compiled into this repository\u0027s AGENTS.md.\nembeds:\n - typeference/profiles/spec-conformance@0.1.0\n - typeference/profiles/determinism-guardian@0.1.0\n - typeference/profiles/trust-signing@0.1.0\n - typeference/profiles/contribution-workflow@0.1.0\nslots:\n repositoryMap: context/repository-map.md\ncontextFiles:\n - context/repository-map.md\n" + "path": "agents/typeference-maintainer.agent.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: agent\nid: typeference/typeference-maintainer@0.1.0\ndisplayName: TypeFerence Maintainer\ndescription: The agent that maintains the TypeFerence repository, defined in TypeFerence\u0027s own terms and compiled into this repository\u0027s AGENTS.md.\nembeds:\n - typeference/profiles/spec-conformance@0.1.0\n - typeference/profiles/determinism-guardian@0.1.0\n - typeference/profiles/trust-signing@0.1.0\n - typeference/profiles/contribution-workflow@0.1.0\nslots:\n repositoryMap: context/repository-map.md\ncontextFiles:\n - context/repository-map.md\n---\n" }, { - "path": "capabilities/audit-drift.capability.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: capability\nid: typeference/capabilities/audit-drift@0.1.0\ndisplayName: Audit Self-Host Drift\ndescription: Recompile the maintainer definition and report drift against the committed artifacts.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022clean\u0022:{\u0022type\u0022:\u0022boolean\u0022},\u0022driftedFiles\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022clean\u0022,\u0022driftedFiles\u0022]}\u0027\n" + "path": "capabilities/audit-drift.capability.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: capability\nid: typeference/capabilities/audit-drift@0.1.0\ndisplayName: Audit Self-Host Drift\ndescription: Recompile the maintainer definition and report drift against the committed artifacts.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022clean\u0022:{\u0022type\u0022:\u0022boolean\u0022},\u0022driftedFiles\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022clean\u0022,\u0022driftedFiles\u0022]}\u0027\n---\n" }, { - "path": "capabilities/verify-conformance.capability.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: capability\nid: typeference/capabilities/verify-conformance@0.1.0\ndisplayName: Verify Conformance\ndescription: Run both implementations against the shared conformance suite and report agreement.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022passed\u0022:{\u0022type\u0022:\u0022boolean\u0022},\u0022mismatches\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022passed\u0022,\u0022mismatches\u0022]}\u0027\n" + "path": "capabilities/verify-conformance.capability.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: capability\nid: typeference/capabilities/verify-conformance@0.1.0\ndisplayName: Verify Conformance\ndescription: Run the determinism suite and report whether the compiler reproduces the committed digests.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022passed\u0022:{\u0022type\u0022:\u0022boolean\u0022},\u0022mismatches\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022passed\u0022,\u0022mismatches\u0022]}\u0027\n---\n" }, { "path": "context/contribution.md", "mediaType": "text/markdown", - "content": "# Contribution workflow\n\n- Work on feature branches; never rewrite published history; never tag or publish a\n release outside the checklist in \u0060docs/release-checklist.md\u0060.\n- Before any commit: \u0060dotnet test TypeFerence.slnx\u0060, \u0060go test ./...\u0060 (from \u0060go/\u0060),\n and the conformance suite (\u0060make conformance\u0060) all pass. A commit that breaks any\n of them does not land.\n- Design decisions with real tradeoffs \u2014 spec semantics, canonical bytes, trust\n model, dependencies \u2014 are recorded in \u0060docs/decisions/\u0060 as numbered ADRs in the\n same change.\n- Generated artifacts (root \u0060AGENTS.md\u0060, \u0060dist/\u0060) are only ever changed by\n regenerating them from source (\u0060make selfhost\u0060, \u0060typeference build\u0060); hand edits\n to generated files are drift and CI rejects them.\n- Documentation is accurate against the code at the commit that includes it. The\n project describes itself as an experimental reference implementation; no invented\n adoption, users, benchmarks, or endorsements, ever.\n" + "content": "# Contribution workflow\n\n- Work on feature branches; never rewrite published history; never tag or publish a\n release outside the checklist in \u0060docs/release-checklist.md\u0060.\n- Before any commit: \u0060go test ./...\u0060 (from \u0060go/\u0060) and the determinism suite\n (\u0060make conformance\u0060) both pass. A commit that breaks either does not land.\n- Design decisions with real tradeoffs \u2014 spec semantics, canonical bytes, trust\n model, dependencies \u2014 are recorded in \u0060docs/decisions/\u0060 as numbered ADRs in the\n same change.\n- Generated artifacts (root \u0060AGENTS.md\u0060, \u0060dist/\u0060) are only ever changed by\n regenerating them from source (\u0060make selfhost\u0060, \u0060typeference build\u0060); hand edits\n to generated files are drift and CI rejects them.\n- Documentation is accurate against the code at the commit that includes it. The\n project describes itself as an experimental reference implementation; no invented\n adoption, users, benchmarks, or endorsements, ever.\n" }, { "path": "context/determinism.md", "mediaType": "text/markdown", - "content": "# Determinism guarantees\n\nIdentical source must compile to identical bytes \u2014 across repeated builds, across\nplatforms, and across implementations. The guarantee is enforced, not aspirational:\n\n- \u0060conformance/fixtures/\u0060 records the expected \u0060typeference-directory-v1\u0060 digest of\n every emitted target for 25\u002B fixtures. Both implementations run the corpus in CI.\n- The committed \u0060dist/\u0060 tree is the fully materialized reference output for\n \u0060examples/helio\u0060; both implementations byte-compare against it in their own tests.\n- The committed root \u0060AGENTS.md\u0060 and \u0060dist-maintainer/\u0060 are build artifacts of\n \u0060agents/maintainer/\u0060; CI recompiles the definition and fails on any drift.\n\nRules that protect the guarantee:\n\n- Digest values are regenerated (\u0060go test ./conformance -update\u0060, then verified by\n the C# runner), never typed by hand.\n- Canonical serialization is defined in \u0060docs/specification.md\u0060 (\u0022Deterministic\n compilation\u0022); any change to it is a specification change with an ADR.\n- Nothing about determinism, provenance, or fail-closed behavior is ever relaxed to\n make an unrelated change easier. If a change fights the determinism rules, the\n change is wrong or the specification needs a recorded amendment.\n" + "content": "# Determinism guarantees\n\nIdentical source must compile to identical bytes \u2014 across repeated builds and\nacross platforms. The guarantee is enforced, not aspirational:\n\n- \u0060conformance/fixtures/\u0060 records the expected \u0060typeference-directory-v1\u0060 digest of\n every emitted target for the fixture corpus; the compiler must reproduce them in\n CI (a golden-file determinism suite).\n- The committed \u0060dist/\u0060 tree is the fully materialized reference output for\n \u0060examples/helio\u0060; the compiler byte-compares against it in its tests.\n- The committed root \u0060AGENTS.md\u0060 and \u0060dist-maintainer/\u0060 are build artifacts of\n \u0060agents/maintainer/\u0060; CI recompiles the definition and fails on any drift.\n\nRules that protect the guarantee:\n\n- Digest values are regenerated (\u0060go test ./conformance -update\u0060), never typed by\n hand.\n- Canonical serialization is defined in \u0060docs/specification.md\u0060 (\u0022Deterministic\n compilation\u0022); any change to it is a specification change with an ADR.\n- Nothing about determinism, provenance, or fail-closed behavior is ever relaxed to\n make an unrelated change easier. If a change fights the determinism rules, the\n change is wrong or the specification needs a recorded amendment.\n" }, { "path": "context/repository-map.md", "mediaType": "text/markdown", - "content": "# Repository map\n\n| Path | Role |\n| --- | --- |\n| \u0060docs/specification.md\u0060 | Normative specification (source of truth). |\n| \u0060docs/whitepaper.md\u0060 | Motivation and design narrative. |\n| \u0060docs/decisions/\u0060 | Architecture decision records. |\n| \u0060src/\u0060 | C# reference implementation (.NET 10). |\n| \u0060go/\u0060 | Go implementation (static binary; module \u0060github.com/buchk/TypeFerence/go\u0060). |\n| \u0060tests/\u0060 | C# test suite, including the conformance runner. |\n| \u0060go/conformance/\u0060 | Go conformance runner (\u0060-update\u0060 regenerates digests). |\n| \u0060conformance/\u0060 | Shared cross-implementation fixture corpus. |\n| \u0060examples/helio\u0060 | Example organization used by tests and the quick start. |\n| \u0060dist/\u0060 | Committed reference output of \u0060examples/helio\u0060 (byte-compared in CI). |\n| \u0060agents/maintainer/\u0060 | This definition; compiled into the root \u0060AGENTS.md\u0060 and \u0060dist-maintainer/\u0060. |\n| \u0060.github/workflows/ci.yml\u0060 | Build, test, conformance, and self-host drift gates. |\n\nThe root \u0060AGENTS.md\u0060 is generated from this definition. To change it, edit the\nresources under \u0060agents/maintainer/\u0060 and run \u0060make selfhost\u0060; never edit the\ngenerated file directly.\n" + "content": "# Repository map\n\n| Path | Role |\n| --- | --- |\n| \u0060docs/specification.md\u0060 | Normative specification (source of truth). |\n| \u0060docs/whitepaper.md\u0060 | Motivation and design narrative. |\n| \u0060docs/decisions/\u0060 | Architecture decision records. |\n| \u0060go/\u0060 | The implementation (static binary; module \u0060github.com/buchk/TypeFerence/go\u0060). |\n| \u0060go/cmd/typeference-lsp/\u0060 | Language server for \u0060.tfer\u0060/\u0060.yaml\u0060 authoring. |\n| \u0060editors/vscode/\u0060 | VS Code client for the language server. |\n| \u0060go/conformance/\u0060 | Determinism runner (\u0060-update\u0060 regenerates digests). |\n| \u0060conformance/\u0060 | Golden-file fixture corpus. |\n| \u0060examples/helio\u0060 | Example organization used by tests and the quick start. |\n| \u0060dist/\u0060 | Committed reference output of \u0060examples/helio\u0060 (byte-compared in CI). |\n| \u0060agents/maintainer/\u0060 | This definition; compiled into the root \u0060AGENTS.md\u0060 and \u0060dist-maintainer/\u0060. |\n| \u0060.github/workflows/ci.yml\u0060 | Build, test, determinism, and self-host drift gates. |\n\nThe root \u0060AGENTS.md\u0060 is generated from this definition. To change it, edit the\nresources under \u0060agents/maintainer/\u0060 and run \u0060make selfhost\u0060; never edit the\ngenerated file directly.\n" }, { "path": "context/spec-workflow.md", "mediaType": "text/markdown", - "content": "# Specification-first workflow\n\nThe normative specification is \u0060docs/specification.md\u0060 at the repository root. It is\nthe source of truth for both implementations:\n\n- C# reference implementation: \u0060src/TypeFerence.Core\u0060, \u0060src/TypeFerence.Cli\u0060\n- Go implementation: \u0060go/\u0060\n\nA change is *semantic* when it alters what source trees are valid, how composition\nresolves, or what bytes compilation emits. Semantic changes follow this order, in one\nreviewable change set:\n\n1. Amend \u0060docs/specification.md\u0060.\n2. Record the decision and rejected alternatives as an ADR in \u0060docs/decisions/\u0060.\n3. Add or update fixtures in \u0060conformance/fixtures/\u0060 capturing the ruling.\n4. Update both implementations until the conformance suite passes on both.\n\nIf an implementation is found to disagree with the specification, the specification\nwins. If the specification is ambiguous enough that two honest implementations could\ndiverge, that is a specification bug: fix the text, do not encode a private\ninterpretation in code.\n" + "content": "# Specification-first workflow\n\nThe normative specification is \u0060docs/specification.md\u0060 at the repository root. It is\nthe source of truth; the Go implementation under \u0060go/\u0060 is its reference realization,\nand the spec stays normative in principle so another implementation could be built\nagainst it (ADR-0014).\n\nA change is *semantic* when it alters what source trees are valid, how composition\nresolves, or what bytes compilation emits. Semantic changes follow this order, in one\nreviewable change set:\n\n1. Amend \u0060docs/specification.md\u0060.\n2. Record the decision and rejected alternatives as an ADR in \u0060docs/decisions/\u0060.\n3. Add or update fixtures in \u0060conformance/fixtures/\u0060 capturing the ruling.\n4. Update the implementation until the determinism suite passes.\n\nIf the implementation is found to disagree with the specification, the specification\nwins. If the specification is ambiguous, that is a specification bug: fix the text,\ndo not encode a private interpretation in code.\n" }, { "path": "context/trust-model.md", "mediaType": "text/markdown", - "content": "# Trust model invariants\n\nThe trust model is declarative supply-chain metadata, specified in\n\u0060docs/specification.md\u0060 (\u0022Trust metadata compilation\u0022 and \u0022Trust Manifest\nsignatures\u0022). The invariants that must survive every change:\n\n- **The signature map lives outside the source root.** The source-package digest\n covers every file under the source root; a signature stored inside would change\n the digest embedded in the very payload being signed. Both implementations reject\n a signature map path beneath the source root.\n- **Fail-closed stays fail-closed.** When \u0060signatureIntent.required\u0060 is true and no\n signature is imported, publication fails. \u0060--allow-unsigned-trust\u0060 exists solely\n to emit the payload for an external signer, and its output is visibly unsigned.\n- **TypeFerence never signs.** It imports externally produced compact detached JWS\n strings, validates their shape, and never verifies cryptographic validity,\n resolves keys, or dereferences any trust URI.\n- **Identities are ASCII** (punycode for internationalized authorities) so publisher\n alignment does not depend on implementation-specific IDN handling.\n\nChanges that touch any of these need a specification amendment, an ADR, and\nconformance fixtures before implementation.\n" + "content": "# Trust model invariants\n\nThe trust model is declarative supply-chain metadata, specified in\n\u0060docs/specification.md\u0060 (\u0022Trust metadata compilation\u0022 and \u0022Trust Manifest\nsignatures\u0022). The invariants that must survive every change:\n\n- **The signature map lives outside the source root.** The source-package digest\n covers every file under the source root; a signature stored inside would change\n the digest embedded in the very payload being signed. The compiler rejects a\n signature map path beneath the source root.\n- **Fail-closed stays fail-closed.** When \u0060signatureIntent.required\u0060 is true and no\n signature is imported, publication fails. \u0060--allow-unsigned-trust\u0060 exists solely\n to emit the payload for an external signer, and its output is visibly unsigned.\n- **TypeFerence never signs.** It imports externally produced compact detached JWS\n strings, validates their shape, and never verifies cryptographic validity,\n resolves keys, or dereferences any trust URI.\n- **Identities are ASCII** (punycode for internationalized authorities) so publisher\n alignment does not depend on implementation-specific IDN handling.\n\nChanges that touch any of these need a specification amendment, an ADR, and\nconformance fixtures before implementation.\n" }, { - "path": "profiles/contribution-workflow.profile.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: profile\nid: typeference/profiles/contribution-workflow@0.1.0\ndisplayName: Contribution Workflow\ndescription: How changes to this repository are made, reviewed, and recorded.\nworkingNorms:\n - Every commit builds and passes both implementations\u0027 test suites and the shared conformance suite.\n - Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges.\n - Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements.\n - Commit messages are conventional and written for a critical human reader.\ncontextFiles:\n - context/contribution.md\n" + "path": "profiles/contribution-workflow.profile.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: profile\nid: typeference/profiles/contribution-workflow@0.1.0\ndisplayName: Contribution Workflow\ndescription: How changes to this repository are made, reviewed, and recorded.\nworkingNorms:\n - Every commit builds and passes the test suite and the determinism suite.\n - Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges.\n - Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements.\n - Commit messages are conventional and written for a critical human reader.\ncontextFiles:\n - context/contribution.md\n---\n" }, { - "path": "profiles/determinism-guardian.profile.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: profile\nid: typeference/profiles/determinism-guardian@0.1.0\ndisplayName: Determinism Guardian\ndescription: Any codegen change must preserve byte-identical output across both implementations.\nworkingNorms:\n - Any change to code generation must keep the C# and Go implementations byte-identical, verified by the conformance suite before merge.\n - Never weaken determinism, provenance, or fail-closed behavior to make a change easier.\n - A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest.\n - Repeated builds from identical source must stay byte-identical on every platform.\ncontextFiles:\n - context/determinism.md\nskills:\n - ref: typeference/skills/verify-conformance@0.1.0\n - ref: typeference/skills/audit-drift@0.1.0\n" + "path": "profiles/determinism-guardian.profile.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: profile\nid: typeference/profiles/determinism-guardian@0.1.0\ndisplayName: Determinism Guardian\ndescription: Any codegen change must preserve byte-identical output, verified by the determinism suite.\nworkingNorms:\n - Any change to code generation must reproduce the committed digests, verified by the determinism suite before merge.\n - Never weaken determinism, provenance, or fail-closed behavior to make a change easier.\n - A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest.\n - Repeated builds from identical source must stay byte-identical on every platform.\ncontextFiles:\n - context/determinism.md\nskills:\n - ref: typeference/skills/verify-conformance@0.1.0\n - ref: typeference/skills/audit-drift@0.1.0\n---\n" }, { - "path": "profiles/spec-conformance.profile.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: profile\nid: typeference/profiles/spec-conformance@0.1.0\ndisplayName: Spec Conformance\ndescription: Semantic changes land in the specification first, then in both implementations, with conformance fixtures.\nworkingNorms:\n - Semantic changes land in docs/specification.md before either implementation changes behavior.\n - Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture.\n - Every canonicalization or composition ruling ships with a fixture under conformance/fixtures in the same change.\n - Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not.\ncontextFiles:\n - context/spec-workflow.md\n" + "path": "profiles/spec-conformance.profile.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: profile\nid: typeference/profiles/spec-conformance@0.1.0\ndisplayName: Spec Conformance\ndescription: Semantic changes land in the specification first, then in the implementation, with conformance fixtures.\nworkingNorms:\n - Semantic changes land in docs/specification.md before either implementation changes behavior.\n - Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture.\n - Every canonicalization or composition ruling ships with a fixture under conformance/fixtures in the same change.\n - Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not.\ncontextFiles:\n - context/spec-workflow.md\n---\n" }, { - "path": "profiles/trust-signing.profile.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: profile\nid: typeference/profiles/trust-signing@0.1.0\ndisplayName: Trust and Signing\ndescription: Preserves the supply-chain trust model exactly as specified.\nworkingNorms:\n - The signature map always resides outside the source root; moving it inside creates a digest/signature cycle and is forbidden.\n - signatureIntent.required fails closed; the unsigned-staging option exists solely to emit payloads for an external signer.\n - TypeFerence imports externally produced signatures; it never signs, never verifies cryptographic validity, and never resolves keys.\n - Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation.\ncontextFiles:\n - context/trust-model.md\n" + "path": "profiles/trust-signing.profile.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: profile\nid: typeference/profiles/trust-signing@0.1.0\ndisplayName: Trust and Signing\ndescription: Preserves the supply-chain trust model exactly as specified.\nworkingNorms:\n - The signature map always resides outside the source root; moving it inside creates a digest/signature cycle and is forbidden.\n - signatureIntent.required fails closed; the unsigned-staging option exists solely to emit payloads for an external signer.\n - TypeFerence imports externally produced signatures; it never signs, never verifies cryptographic validity, and never resolves keys.\n - Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation.\ncontextFiles:\n - context/trust-model.md\n---\n" }, { - "path": "skills/audit-drift.skill.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: skill\nid: typeference/skills/audit-drift@0.1.0\nbinds: typeference/capabilities/audit-drift@0.1.0\ndisplayName: Audit Self-Host Drift Skill\ndescription: Confirms the committed AGENTS.md and maintainer bundle are exact build artifacts of this definition.\ninstructions: |\n Run \u0060typeference diff agents/maintainer --against dist-maintainer --target neutral\n --emit-ard --publisher-domain typeference.example\u0060 from the repository root, then\n byte-compare the repository-root AGENTS.md against\n dist-maintainer/neutral/typeference-maintainer/AGENTS.md. Report clean=true only\n when the diff exits 0 and the byte comparison matches. Any drift between the\n definition and its committed artifacts is a broken build: regenerate with\n \u0060make selfhost\u0060 and commit definition and artifacts together, or revert the\n stray edit to the generated files.\ncontextFiles:\n - context/determinism.md\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022clean\u0022:{\u0022type\u0022:\u0022boolean\u0022},\u0022driftedFiles\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022clean\u0022,\u0022driftedFiles\u0022]}\u0027\n" + "path": "skills/audit-drift.skill.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: skill\nid: typeference/skills/audit-drift@0.1.0\nbinds: typeference/capabilities/audit-drift@0.1.0\ndisplayName: Audit Self-Host Drift Skill\ndescription: Confirms the committed AGENTS.md and maintainer bundle are exact build artifacts of this definition.\ninstructions: |\n Run \u0060typeference diff agents/maintainer --against dist-maintainer --target neutral\n --emit-ard --publisher-domain typeference.example\u0060 from the repository root, then\n byte-compare the repository-root AGENTS.md against\n dist-maintainer/neutral/typeference-maintainer/AGENTS.md. Report clean=true only\n when the diff exits 0 and the byte comparison matches. Any drift between the\n definition and its committed artifacts is a broken build: regenerate with\n \u0060make selfhost\u0060 and commit definition and artifacts together, or revert the\n stray edit to the generated files.\ncontextFiles:\n - context/determinism.md\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022clean\u0022:{\u0022type\u0022:\u0022boolean\u0022},\u0022driftedFiles\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022clean\u0022,\u0022driftedFiles\u0022]}\u0027\n---\n" }, { - "path": "skills/verify-conformance.skill.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: skill\nid: typeference/skills/verify-conformance@0.1.0\nbinds: typeference/capabilities/verify-conformance@0.1.0\ndisplayName: Verify Conformance Skill\ndescription: Runs the shared conformance suite on both implementations and reports any digest disagreement.\ninstructions: |\n Run \u0060go test ./conformance\u0060 from the go/ directory, then run\n \u0060dotnet test TypeFerence.slnx --filter FullyQualifiedName~ConformanceSuiteTests\u0060\n from the repository root (or \u0060make conformance\u0060 for both). Report passed=true\n only when every fixture passes on both implementations. List each failing\n fixture and target as a mismatch. Never resolve a mismatch by editing a\n digest; find the diverging implementation or take the ruling to the\n specification with an ADR and a new fixture.\ncontextFiles:\n - context/determinism.md\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022passed\u0022:{\u0022type\u0022:\u0022boolean\u0022},\u0022mismatches\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022passed\u0022,\u0022mismatches\u0022]}\u0027\n" + "path": "skills/verify-conformance.skill.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: skill\nid: typeference/skills/verify-conformance@0.1.0\nbinds: typeference/capabilities/verify-conformance@0.1.0\ndisplayName: Verify Conformance Skill\ndescription: Runs the determinism suite and reports whether the compiler reproduces the committed digests.\ninstructions: |\n Run \u0060go test ./conformance\u0060 from the go/ directory (or \u0060make conformance\u0060).\n Report passed=true only when every fixture reproduces its committed digest.\n List each failing fixture and target as a mismatch. Never resolve a mismatch\n by editing a digest; find the compiler regression, or take the ruling to the\n specification with an ADR and a regenerated fixture.\ncontextFiles:\n - context/determinism.md\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022passed\u0022:{\u0022type\u0022:\u0022boolean\u0022},\u0022mismatches\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022passed\u0022,\u0022mismatches\u0022]}\u0027\n---\n" } ] }, @@ -110,12 +110,12 @@ { "path": "AGENTS.md", "mediaType": "text/markdown", - "content": "# TypeFerence Maintainer\n\nThe agent that maintains the TypeFerence repository, defined in TypeFerence\u0027s own terms and compiled into this repository\u0027s AGENTS.md.\n\n## Working norms\n\n- Semantic changes land in docs/specification.md before either implementation changes behavior.\n- Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture.\n- Every canonicalization or composition ruling ships with a fixture under conformance/fixtures in the same change.\n- Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not.\n- Any change to code generation must keep the C# and Go implementations byte-identical, verified by the conformance suite before merge.\n- Never weaken determinism, provenance, or fail-closed behavior to make a change easier.\n- A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest.\n- Repeated builds from identical source must stay byte-identical on every platform.\n- The signature map always resides outside the source root; moving it inside creates a digest/signature cycle and is forbidden.\n- signatureIntent.required fails closed; the unsigned-staging option exists solely to emit payloads for an external signer.\n- TypeFerence imports externally produced signatures; it never signs, never verifies cryptographic validity, and never resolves keys.\n- Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation.\n- Every commit builds and passes both implementations\u0027 test suites and the shared conformance suite.\n- Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges.\n- Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements.\n- Commit messages are conventional and written for a critical human reader.\n\n## Context slots\n\n- \u0060repositoryMap\u0060: \u0060context/repository-map.md\u0060\n\n## Available skills\n\n- \u0060typeference-maintainer.audit-drift\u0060: Confirms the committed AGENTS.md and maintainer bundle are exact build artifacts of this definition.\n- \u0060typeference-maintainer.verify-conformance\u0060: Runs the shared conformance suite on both implementations and reports any digest disagreement.\n\n" + "content": "# TypeFerence Maintainer\n\nThe agent that maintains the TypeFerence repository, defined in TypeFerence\u0027s own terms and compiled into this repository\u0027s AGENTS.md.\n\n## Working norms\n\n- Semantic changes land in docs/specification.md before either implementation changes behavior.\n- Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture.\n- Every canonicalization or composition ruling ships with a fixture under conformance/fixtures in the same change.\n- Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not.\n- Any change to code generation must reproduce the committed digests, verified by the determinism suite before merge.\n- Never weaken determinism, provenance, or fail-closed behavior to make a change easier.\n- A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest.\n- Repeated builds from identical source must stay byte-identical on every platform.\n- The signature map always resides outside the source root; moving it inside creates a digest/signature cycle and is forbidden.\n- signatureIntent.required fails closed; the unsigned-staging option exists solely to emit payloads for an external signer.\n- TypeFerence imports externally produced signatures; it never signs, never verifies cryptographic validity, and never resolves keys.\n- Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation.\n- Every commit builds and passes the test suite and the determinism suite.\n- Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges.\n- Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements.\n- Commit messages are conventional and written for a critical human reader.\n\n## Context slots\n\n- \u0060repositoryMap\u0060: \u0060context/repository-map.md\u0060\n\n## Available skills\n\n- \u0060typeference-maintainer.audit-drift\u0060: Confirms the committed AGENTS.md and maintainer bundle are exact build artifacts of this definition.\n- \u0060typeference-maintainer.verify-conformance\u0060: Runs the determinism suite and reports whether the compiler reproduces the committed digests.\n\n" }, { "path": "bundle.json", "mediaType": "application/json", - "content": "{\n \u0022id\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022,\n \u0022displayName\u0022: \u0022TypeFerence Maintainer\u0022,\n \u0022description\u0022: \u0022The agent that maintains the TypeFerence repository, defined in TypeFerence\\u0027s own terms and compiled into this repository\\u0027s AGENTS.md.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022typeference/profiles/spec-conformance@0.1.0\u0022,\n \u0022typeference/profiles/determinism-guardian@0.1.0\u0022,\n \u0022typeference/profiles/trust-signing@0.1.0\u0022,\n \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n ],\n \u0022satisfies\u0022: [],\n \u0022slots\u0022: {\n \u0022repositoryMap\u0022: \u0022context/repository-map.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Semantic changes land in docs/specification.md before either implementation changes behavior.\u0022,\n \u0022Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture.\u0022,\n \u0022Every canonicalization or composition ruling ships with a fixture under conformance/fixtures in the same change.\u0022,\n \u0022Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not.\u0022,\n \u0022Any change to code generation must keep the C# and Go implementations byte-identical, verified by the conformance suite before merge.\u0022,\n \u0022Never weaken determinism, provenance, or fail-closed behavior to make a change easier.\u0022,\n \u0022A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest.\u0022,\n \u0022Repeated builds from identical source must stay byte-identical on every platform.\u0022,\n \u0022The signature map always resides outside the source root; moving it inside creates a digest/signature cycle and is forbidden.\u0022,\n \u0022signatureIntent.required fails closed; the unsigned-staging option exists solely to emit payloads for an external signer.\u0022,\n \u0022TypeFerence imports externally produced signatures; it never signs, never verifies cryptographic validity, and never resolves keys.\u0022,\n \u0022Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation.\u0022,\n \u0022Every commit builds and passes both implementations\\u0027 test suites and the shared conformance suite.\u0022,\n \u0022Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges.\u0022,\n \u0022Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements.\u0022,\n \u0022Commit messages are conventional and written for a critical human reader.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/spec-workflow.md\u0022,\n \u0022context/determinism.md\u0022,\n \u0022context/trust-model.md\u0022,\n \u0022context/contribution.md\u0022,\n \u0022context/repository-map.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022typeference-maintainer.audit-drift\u0022,\n \u0022capabilityId\u0022: \u0022typeference/capabilities/audit-drift@0.1.0\u0022,\n \u0022implementationId\u0022: \u0022typeference/skills/audit-drift@0.1.0\u0022,\n \u0022description\u0022: \u0022Confirms the committed AGENTS.md and maintainer bundle are exact build artifacts of this definition.\u0022,\n \u0022instructions\u0022: \u0022Run \\u0060typeference diff agents/maintainer --against dist-maintainer --target neutral\\n--emit-ard --publisher-domain typeference.example\\u0060 from the repository root, then\\nbyte-compare the repository-root AGENTS.md against\\ndist-maintainer/neutral/typeference-maintainer/AGENTS.md. Report clean=true only\\nwhen the diff exits 0 and the byte comparison matches. Any drift between the\\ndefinition and its committed artifacts is a broken build: regenerate with\\n\\u0060make selfhost\\u0060 and commit definition and artifacts together, or revert the\\nstray edit to the generated files.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022clean\\u0022:{\\u0022type\\u0022:\\u0022boolean\\u0022},\\u0022driftedFiles\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022clean\\u0022,\\u0022driftedFiles\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/determinism.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022typeference/capabilities/audit-drift@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022typeference/skills/audit-drift@0.1.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022typeference-maintainer.verify-conformance\u0022,\n \u0022capabilityId\u0022: \u0022typeference/capabilities/verify-conformance@0.1.0\u0022,\n \u0022implementationId\u0022: \u0022typeference/skills/verify-conformance@0.1.0\u0022,\n \u0022description\u0022: \u0022Runs the shared conformance suite on both implementations and reports any digest disagreement.\u0022,\n \u0022instructions\u0022: \u0022Run \\u0060go test ./conformance\\u0060 from the go/ directory, then run\\n\\u0060dotnet test TypeFerence.slnx --filter FullyQualifiedName~ConformanceSuiteTests\\u0060\\nfrom the repository root (or \\u0060make conformance\\u0060 for both). Report passed=true\\nonly when every fixture passes on both implementations. List each failing\\nfixture and target as a mismatch. Never resolve a mismatch by editing a\\ndigest; find the diverging implementation or take the ruling to the\\nspecification with an ADR and a new fixture.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022passed\\u0022:{\\u0022type\\u0022:\\u0022boolean\\u0022},\\u0022mismatches\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022passed\\u0022,\\u0022mismatches\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/determinism.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022typeference/capabilities/verify-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022typeference/skills/verify-conformance@0.1.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.typeference/profiles/spec-conformance@0.1.0\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.typeference/profiles/determinism-guardian@0.1.0\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.typeference/profiles/trust-signing@0.1.0\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.typeference/profiles/contribution-workflow@0.1.0\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repositoryMap\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n }\n ]\n}\n" + "content": "{\n \u0022id\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022,\n \u0022displayName\u0022: \u0022TypeFerence Maintainer\u0022,\n \u0022description\u0022: \u0022The agent that maintains the TypeFerence repository, defined in TypeFerence\\u0027s own terms and compiled into this repository\\u0027s AGENTS.md.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022typeference/profiles/spec-conformance@0.1.0\u0022,\n \u0022typeference/profiles/determinism-guardian@0.1.0\u0022,\n \u0022typeference/profiles/trust-signing@0.1.0\u0022,\n \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n ],\n \u0022satisfies\u0022: [],\n \u0022slots\u0022: {\n \u0022repositoryMap\u0022: \u0022context/repository-map.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Semantic changes land in docs/specification.md before either implementation changes behavior.\u0022,\n \u0022Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture.\u0022,\n \u0022Every canonicalization or composition ruling ships with a fixture under conformance/fixtures in the same change.\u0022,\n \u0022Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not.\u0022,\n \u0022Any change to code generation must reproduce the committed digests, verified by the determinism suite before merge.\u0022,\n \u0022Never weaken determinism, provenance, or fail-closed behavior to make a change easier.\u0022,\n \u0022A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest.\u0022,\n \u0022Repeated builds from identical source must stay byte-identical on every platform.\u0022,\n \u0022The signature map always resides outside the source root; moving it inside creates a digest/signature cycle and is forbidden.\u0022,\n \u0022signatureIntent.required fails closed; the unsigned-staging option exists solely to emit payloads for an external signer.\u0022,\n \u0022TypeFerence imports externally produced signatures; it never signs, never verifies cryptographic validity, and never resolves keys.\u0022,\n \u0022Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation.\u0022,\n \u0022Every commit builds and passes the test suite and the determinism suite.\u0022,\n \u0022Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges.\u0022,\n \u0022Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements.\u0022,\n \u0022Commit messages are conventional and written for a critical human reader.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/spec-workflow.md\u0022,\n \u0022context/determinism.md\u0022,\n \u0022context/trust-model.md\u0022,\n \u0022context/contribution.md\u0022,\n \u0022context/repository-map.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022typeference-maintainer.audit-drift\u0022,\n \u0022capabilityId\u0022: \u0022typeference/capabilities/audit-drift@0.1.0\u0022,\n \u0022implementationId\u0022: \u0022typeference/skills/audit-drift@0.1.0\u0022,\n \u0022description\u0022: \u0022Confirms the committed AGENTS.md and maintainer bundle are exact build artifacts of this definition.\u0022,\n \u0022instructions\u0022: \u0022Run \\u0060typeference diff agents/maintainer --against dist-maintainer --target neutral\\n--emit-ard --publisher-domain typeference.example\\u0060 from the repository root, then\\nbyte-compare the repository-root AGENTS.md against\\ndist-maintainer/neutral/typeference-maintainer/AGENTS.md. Report clean=true only\\nwhen the diff exits 0 and the byte comparison matches. Any drift between the\\ndefinition and its committed artifacts is a broken build: regenerate with\\n\\u0060make selfhost\\u0060 and commit definition and artifacts together, or revert the\\nstray edit to the generated files.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022clean\\u0022:{\\u0022type\\u0022:\\u0022boolean\\u0022},\\u0022driftedFiles\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022clean\\u0022,\\u0022driftedFiles\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/determinism.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022typeference/capabilities/audit-drift@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022typeference/skills/audit-drift@0.1.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022typeference-maintainer.verify-conformance\u0022,\n \u0022capabilityId\u0022: \u0022typeference/capabilities/verify-conformance@0.1.0\u0022,\n \u0022implementationId\u0022: \u0022typeference/skills/verify-conformance@0.1.0\u0022,\n \u0022description\u0022: \u0022Runs the determinism suite and reports whether the compiler reproduces the committed digests.\u0022,\n \u0022instructions\u0022: \u0022Run \\u0060go test ./conformance\\u0060 from the go/ directory (or \\u0060make conformance\\u0060).\\nReport passed=true only when every fixture reproduces its committed digest.\\nList each failing fixture and target as a mismatch. Never resolve a mismatch\\nby editing a digest; find the compiler regression, or take the ruling to the\\nspecification with an ADR and a regenerated fixture.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022passed\\u0022:{\\u0022type\\u0022:\\u0022boolean\\u0022},\\u0022mismatches\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022passed\\u0022,\\u0022mismatches\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/determinism.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022typeference/capabilities/verify-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022typeference/skills/verify-conformance@0.1.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/profiles/spec-conformance@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/profiles/determinism-guardian@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/profiles/trust-signing@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/profiles/contribution-workflow@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.typeference/profiles/spec-conformance@0.1.0\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.typeference/profiles/determinism-guardian@0.1.0\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.typeference/profiles/trust-signing@0.1.0\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.typeference/profiles/contribution-workflow@0.1.0\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repositoryMap\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022typeference/typeference-maintainer@0.1.0\u0022\n }\n ]\n}\n" }, { "path": "provenance.json", @@ -130,13 +130,13 @@ { "path": "skills/verify-conformance/SKILL.md", "mediaType": "text/markdown", - "content": "---\nname: verify-conformance\ndescription: \u0022Runs the shared conformance suite on both implementations and reports any digest disagreement.\u0022\n---\n\nRun \u0060go test ./conformance\u0060 from the go/ directory, then run\n\u0060dotnet test TypeFerence.slnx --filter FullyQualifiedName~ConformanceSuiteTests\u0060\nfrom the repository root (or \u0060make conformance\u0060 for both). Report passed=true\nonly when every fixture passes on both implementations. List each failing\nfixture and target as a mismatch. Never resolve a mismatch by editing a\ndigest; find the diverging implementation or take the ruling to the\nspecification with an ADR and a new fixture.\n\n## Context loaded on invocation\n\n- \u0060context/determinism.md\u0060\n" + "content": "---\nname: verify-conformance\ndescription: \u0022Runs the determinism suite and reports whether the compiler reproduces the committed digests.\u0022\n---\n\nRun \u0060go test ./conformance\u0060 from the go/ directory (or \u0060make conformance\u0060).\nReport passed=true only when every fixture reproduces its committed digest.\nList each failing fixture and target as a mismatch. Never resolve a mismatch\nby editing a digest; find the compiler regression, or take the ruling to the\nspecification with an ADR and a regenerated fixture.\n\n## Context loaded on invocation\n\n- \u0060context/determinism.md\u0060\n" } ] }, "metadata": { "generatedBy": "TypeFerence", - "sourceDigest": "sha256:c42776752aef743bd24749eca2f06b0da9b27d51b9edfce13984e39e0aa565c2", + "sourceDigest": "sha256:375d7da2bd1b2d11043af257e1e8af1047395592726bba7c6bcc601d72351908", "sourceIdentifier": "urn:air:typeference.example:typeference:source:maintainer", "target": "neutral" }, @@ -147,7 +147,7 @@ { "relation": "derivedFrom", "sourceId": "urn:air:typeference.example:typeference:source:maintainer", - "sourceDigest": "sha256:c42776752aef743bd24749eca2f06b0da9b27d51b9edfce13984e39e0aa565c2" + "sourceDigest": "sha256:375d7da2bd1b2d11043af257e1e8af1047395592726bba7c6bcc601d72351908" } ] } diff --git a/dist-maintainer/neutral/typeference-maintainer/AGENTS.md b/dist-maintainer/neutral/typeference-maintainer/AGENTS.md index 44fb18d..5b68a87 100644 --- a/dist-maintainer/neutral/typeference-maintainer/AGENTS.md +++ b/dist-maintainer/neutral/typeference-maintainer/AGENTS.md @@ -8,7 +8,7 @@ The agent that maintains the TypeFerence repository, defined in TypeFerence's ow - Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture. - Every canonicalization or composition ruling ships with a fixture under conformance/fixtures in the same change. - Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not. -- Any change to code generation must keep the C# and Go implementations byte-identical, verified by the conformance suite before merge. +- Any change to code generation must reproduce the committed digests, verified by the determinism suite before merge. - Never weaken determinism, provenance, or fail-closed behavior to make a change easier. - A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest. - Repeated builds from identical source must stay byte-identical on every platform. @@ -16,7 +16,7 @@ The agent that maintains the TypeFerence repository, defined in TypeFerence's ow - signatureIntent.required fails closed; the unsigned-staging option exists solely to emit payloads for an external signer. - TypeFerence imports externally produced signatures; it never signs, never verifies cryptographic validity, and never resolves keys. - Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation. -- Every commit builds and passes both implementations' test suites and the shared conformance suite. +- Every commit builds and passes the test suite and the determinism suite. - Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges. - Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements. - Commit messages are conventional and written for a critical human reader. @@ -28,5 +28,5 @@ The agent that maintains the TypeFerence repository, defined in TypeFerence's ow ## Available skills - `typeference-maintainer.audit-drift`: Confirms the committed AGENTS.md and maintainer bundle are exact build artifacts of this definition. -- `typeference-maintainer.verify-conformance`: Runs the shared conformance suite on both implementations and reports any digest disagreement. +- `typeference-maintainer.verify-conformance`: Runs the determinism suite and reports whether the compiler reproduces the committed digests. diff --git a/dist-maintainer/neutral/typeference-maintainer/bundle.json b/dist-maintainer/neutral/typeference-maintainer/bundle.json index eb08bf3..c75ea62 100644 --- a/dist-maintainer/neutral/typeference-maintainer/bundle.json +++ b/dist-maintainer/neutral/typeference-maintainer/bundle.json @@ -18,7 +18,7 @@ "Where the specification and an implementation disagree, the specification wins; record the ruling in docs/decisions and cover it with a conformance fixture.", "Every canonicalization or composition ruling ships with a fixture under conformance/fixtures in the same change.", "Never silently diverge from the specification; fixing the specification is allowed, silent divergence is not.", - "Any change to code generation must keep the C# and Go implementations byte-identical, verified by the conformance suite before merge.", + "Any change to code generation must reproduce the committed digests, verified by the determinism suite before merge.", "Never weaken determinism, provenance, or fail-closed behavior to make a change easier.", "A conformance digest is regenerated only together with the specification change and ADR that justify it; never hand-edit a digest.", "Repeated builds from identical source must stay byte-identical on every platform.", @@ -26,7 +26,7 @@ "signatureIntent.required fails closed; the unsigned-staging option exists solely to emit payloads for an external signer.", "TypeFerence imports externally produced signatures; it never signs, never verifies cryptographic validity, and never resolves keys.", "Trust metadata is declarative; never dereference identity, attestation, or provenance URIs during compilation.", - "Every commit builds and passes both implementations\u0027 test suites and the shared conformance suite.", + "Every commit builds and passes the test suite and the determinism suite.", "Decisions with real tradeoffs are recorded as ADRs in docs/decisions before the change merges.", "Documentation must be accurate against the code at the commit that includes it; no fabricated adoption, benchmarks, or endorsements.", "Commit messages are conventional and written for a critical human reader." @@ -65,8 +65,8 @@ "dispatchName": "typeference-maintainer.verify-conformance", "capabilityId": "typeference/capabilities/verify-conformance@0.1.0", "implementationId": "typeference/skills/verify-conformance@0.1.0", - "description": "Runs the shared conformance suite on both implementations and reports any digest disagreement.", - "instructions": "Run \u0060go test ./conformance\u0060 from the go/ directory, then run\n\u0060dotnet test TypeFerence.slnx --filter FullyQualifiedName~ConformanceSuiteTests\u0060\nfrom the repository root (or \u0060make conformance\u0060 for both). Report passed=true\nonly when every fixture passes on both implementations. List each failing\nfixture and target as a mismatch. Never resolve a mismatch by editing a\ndigest; find the diverging implementation or take the ruling to the\nspecification with an ADR and a new fixture.\n", + "description": "Runs the determinism suite and reports whether the compiler reproduces the committed digests.", + "instructions": "Run \u0060go test ./conformance\u0060 from the go/ directory (or \u0060make conformance\u0060).\nReport passed=true only when every fixture reproduces its committed digest.\nList each failing fixture and target as a mismatch. Never resolve a mismatch\nby editing a digest; find the compiler regression, or take the ruling to the\nspecification with an ADR and a regenerated fixture.\n", "inputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022additionalProperties\u0022:false}", "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022passed\u0022:{\u0022type\u0022:\u0022boolean\u0022},\u0022mismatches\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022passed\u0022,\u0022mismatches\u0022]}", "contextFiles": [ diff --git a/dist-maintainer/neutral/typeference-maintainer/skills/verify-conformance/SKILL.md b/dist-maintainer/neutral/typeference-maintainer/skills/verify-conformance/SKILL.md index 4ee255d..4e65afb 100644 --- a/dist-maintainer/neutral/typeference-maintainer/skills/verify-conformance/SKILL.md +++ b/dist-maintainer/neutral/typeference-maintainer/skills/verify-conformance/SKILL.md @@ -1,15 +1,13 @@ --- name: verify-conformance -description: "Runs the shared conformance suite on both implementations and reports any digest disagreement." +description: "Runs the determinism suite and reports whether the compiler reproduces the committed digests." --- -Run `go test ./conformance` from the go/ directory, then run -`dotnet test TypeFerence.slnx --filter FullyQualifiedName~ConformanceSuiteTests` -from the repository root (or `make conformance` for both). Report passed=true -only when every fixture passes on both implementations. List each failing -fixture and target as a mismatch. Never resolve a mismatch by editing a -digest; find the diverging implementation or take the ruling to the -specification with an ADR and a new fixture. +Run `go test ./conformance` from the go/ directory (or `make conformance`). +Report passed=true only when every fixture reproduces its committed digest. +List each failing fixture and target as a mismatch. Never resolve a mismatch +by editing a digest; find the compiler regression, or take the ruling to the +specification with an ADR and a regenerated fixture. ## Context loaded on invocation diff --git a/dist/ard/ai-catalog.json b/dist/ard/ai-catalog.json index c2812d9..ea0002e 100644 --- a/dist/ard/ai-catalog.json +++ b/dist/ard/ai-catalog.json @@ -13,32 +13,37 @@ "version": "1.0.0", "data": { "schemaVersion": 1, - "digest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26", + "digest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", "files": [ { - "path": "agents/executive-assistant.agent.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: agent\nid: helio/executive-assistant@1.0.0\ndisplayName: Helio Executive Assistant\ndescription: Coordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\nembeds:\n - helio/profiles/person-defaults@1.0.0\ncontextFiles:\n - context/executive-rhythm.md\nskills:\n - ref: helio/skills/prepare-brief@1.0.0\n" + "path": "agents/executive-assistant.agent.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: agent\nid: helio/executive-assistant@1.0.0\ndisplayName: Helio Executive Assistant\ndescription: Coordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\nembeds:\n - helio/profiles/person-defaults@1.0.0\ncontextFiles:\n - context/executive-rhythm.md\nskills:\n - ref: helio/skills/prepare-brief@1.0.0\n---\n" }, { - "path": "agents/payments-repo-agent.agent.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: agent\nid: helio/payments-repo-agent@1.0.0\ndisplayName: Helio Payments Repository Agent\ndescription: Specializes repository assistance for the fictional payments service.\nembeds:\n - helio/profiles/repository-defaults@1.0.0\ncontextFiles:\n - context/payments-service.md\nskills:\n - ref: helio/skills/payments-repository-status@1.0.0\n capability: helio/capabilities/repository-status@1.0.0\n" + "path": "agents/payments-repo-agent.agent.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: agent\nid: helio/payments-repo-agent@1.0.0\ndisplayName: Helio Payments Repository Agent\ndescription: Specializes repository assistance for the fictional payments service.\nembeds:\n - helio/profiles/repository-defaults@1.0.0\ncontextFiles:\n - context/payments-service.md\nskills:\n - ref: helio/skills/payments-repository-status@1.0.0\n capability: helio/capabilities/repository-status@1.0.0\n---\n" }, { - "path": "capabilities/prepare-brief.capability.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: capability\nid: helio/capabilities/prepare-brief@1.0.0\ndisplayName: Prepare Brief\ndescription: Capability slot for assembling decision-oriented executive briefs.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022topic\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022topic\u0022],\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022brief\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022openQuestions\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022brief\u0022,\u0022openQuestions\u0022]}\u0027\n" + "path": "capabilities/prepare-brief.capability.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: capability\nid: helio/capabilities/prepare-brief@1.0.0\ndisplayName: Prepare Brief\ndescription: Capability slot for assembling decision-oriented executive briefs.\nvisibility: exposed\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022topic\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022topic\u0022],\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022brief\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022openQuestions\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022brief\u0022,\u0022openQuestions\u0022]}\u0027\n---\n" }, { - "path": "capabilities/repository-status.capability.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: capability\nid: helio/capabilities/repository-status@1.0.0\ndisplayName: Repository Status\ndescription: Capability slot for evidence-backed repository health summaries.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}\u0027\n" + "path": "capabilities/repository-status.capability.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: capability\nid: helio/capabilities/repository-status@1.0.0\ndisplayName: Repository Status\ndescription: Capability slot for evidence-backed repository health summaries.\nvisibility: exposed\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}\u0027\n---\n" }, { - "path": "capabilities/triage-message.capability.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: capability\nid: helio/capabilities/triage-message@1.0.0\ndisplayName: Triage Message\ndescription: Capability slot for classifying inbound messages and selecting accountable next actions.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022message\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022message\u0022],\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022summary\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022urgency\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022owner\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022nextAction\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022summary\u0022,\u0022urgency\u0022,\u0022owner\u0022,\u0022nextAction\u0022]}\u0027\n" + "path": "capabilities/triage-message.capability.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: capability\nid: helio/capabilities/triage-message@1.0.0\ndisplayName: Triage Message\ndescription: Capability slot for classifying inbound messages and selecting accountable next actions.\nvisibility: exposed\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022message\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022message\u0022],\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022summary\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022urgency\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022owner\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022nextAction\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022summary\u0022,\u0022urgency\u0022,\u0022owner\u0022,\u0022nextAction\u0022]}\u0027\n---\n" + }, + { + "path": "context-types/safety-policy.contexttype.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: contextType\nid: helio/context-types/safety-policy@1.0.0\ndisplayName: Safety Policy\ndescription: An organizational safety policy governing what an agent may do without explicit authority.\nschema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022required\u0022:[\u0022authority\u0022],\u0022properties\u0022:{\u0022authority\u0022:{\u0022type\u0022:\u0022string\u0022}}}\u0027\n---\n" }, { "path": "context/executive-rhythm.md", @@ -66,54 +71,64 @@ "content": "# Repository evidence\n\nRepository reports should identify the current branch, working-tree state, relevant checks, and source commit whenever available.\n" }, { - "path": "context/safety-policy.md", - "mediaType": "text/markdown", - "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + "path": "context/safety-policy.context.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: context\nid: helio/context/safety-policy@1.0.0\ncontextType: helio/context-types/safety-policy@1.0.0\ndisplayName: Helio Safety Policy\nauthority: explicit\n---\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" }, { - "path": "interfaces/correspondent.interface.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: interface\nid: helio/interfaces/correspondent@1.0.0\ndisplayName: Correspondent\ndescription: Contract for agents that assess and route communications.\nrequiresSlots:\n - principal\nrequiresCapabilities:\n - helio/capabilities/triage-message@1.0.0\n" + "path": "interfaces/correspondent.interface.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: interface\nid: helio/interfaces/correspondent@1.0.0\ndisplayName: Correspondent\ndescription: Contract for agents that assess and route communications.\nrequiresSlots:\n - principal\nrequiresCapabilities:\n - helio/capabilities/triage-message@1.0.0\n---\n" }, { - "path": "interfaces/repository-observer.interface.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: interface\nid: helio/interfaces/repository-observer@1.0.0\ndisplayName: Repository Observer\ndescription: Contract for agents that can report evidence-backed repository status.\nrequiresSlots:\n - repository\nrequiresCapabilities:\n - helio/capabilities/repository-status@1.0.0\n" + "path": "interfaces/repository-observer.interface.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: interface\nid: helio/interfaces/repository-observer@1.0.0\ndisplayName: Repository Observer\ndescription: Contract for agents that can report evidence-backed repository status.\nrequiresSlots:\n - repository\nrequiresCapabilities:\n - helio/capabilities/repository-status@1.0.0\n---\n" }, { - "path": "profiles/enterprise-defaults.profile.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: profile\nid: helio/profiles/enterprise-defaults@1.0.0\ndisplayName: Helio Enterprise Defaults\ndescription: Shared organizational governance for every Helio resource.\nslots:\n organization: context/organization.md\n safetyPolicy: context/safety-policy.md\nworkingNorms:\n - Preserve a clear audit trail for material decisions.\n - State uncertainty and route work to an accountable owner when authority is unclear.\ncontextFiles:\n - context/organization.md\n - context/safety-policy.md\n" + "path": "profiles/enterprise-defaults.profile.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: profile\nid: helio/profiles/enterprise-defaults@1.0.0\ndisplayName: Helio Enterprise Defaults\ndescription: Shared organizational governance for every Helio resource.\nslots:\n organization: context/organization.md\nworkingNorms:\n - Preserve a clear audit trail for material decisions.\n - State uncertainty and route work to an accountable owner when authority is unclear.\ncontextFiles:\n - context/organization.md\ncontext:\n - helio/context/safety-policy@1.0.0\n---\n" }, { - "path": "profiles/person-defaults.profile.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: profile\nid: helio/profiles/person-defaults@1.0.0\ndisplayName: Helio Person Defaults\ndescription: Reusable behavior for respectful inbound and outbound assistance for a person.\nembeds:\n - helio/profiles/enterprise-defaults@1.0.0\nslots:\n principal: context/principal.md\nworkingNorms:\n - Distinguish drafts from messages approved for delivery.\ncontextFiles:\n - context/principal.md\nskills:\n - ref: helio/skills/triage-message@1.0.0\n" + "path": "profiles/person-defaults.profile.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: profile\nid: helio/profiles/person-defaults@1.0.0\ndisplayName: Helio Person Defaults\ndescription: Reusable behavior for respectful inbound and outbound assistance for a person.\nembeds:\n - helio/profiles/enterprise-defaults@1.0.0\nslots:\n principal: context/principal.md\nworkingNorms:\n - Distinguish drafts from messages approved for delivery.\ncontextFiles:\n - context/principal.md\nskills:\n - ref: helio/skills/triage-message@1.0.0\n sealed: true\n---\n" }, { - "path": "profiles/repository-defaults.profile.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: profile\nid: helio/profiles/repository-defaults@1.0.0\ndisplayName: Helio Repository Defaults\ndescription: Reusable delivery practices for software repository assistance.\nembeds:\n - helio/profiles/enterprise-defaults@1.0.0\nslots:\n repository: context/repository.md\nworkingNorms:\n - Prefer evidence from tests, diffs, and version control over recollection.\ncontextFiles:\n - context/repository.md\nskills:\n - ref: helio/skills/repository-status@1.0.0\n" + "path": "profiles/repository-defaults.profile.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: profile\nid: helio/profiles/repository-defaults@1.0.0\ndisplayName: Helio Repository Defaults\ndescription: Reusable delivery practices for software repository assistance.\nembeds:\n - helio/profiles/enterprise-defaults@1.0.0\nslots:\n repository: context/repository.md\nworkingNorms:\n - Prefer evidence from tests, diffs, and version control over recollection.\ncontextFiles:\n - context/repository.md\nskills:\n - ref: helio/skills/repository-status@1.0.0\n---\n" }, { - "path": "skills/payments-repository-status.skill.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: skill\nid: helio/skills/payments-repository-status@1.0.0\nbinds: helio/capabilities/repository-status@1.0.0\ndisplayName: Payments Repository Status\ndescription: Report payments-service health with contract and reconciliation evidence.\ncontextFiles:\n - context/payments-service.md\ninstructions: |\n Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\n Do not report the service healthy when any required financial-control signal is unavailable.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}\u0027\n" + "path": "skills/payments-repository-status.skill.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: skill\nid: helio/skills/payments-repository-status@1.0.0\nbinds: helio/capabilities/repository-status@1.0.0\ndisplayName: Payments Repository Status\ndescription: Report payments-service health with contract and reconciliation evidence.\ncontextFiles:\n - context/payments-service.md\nrequiresTools:\n - helio/tools/repository-signals@1.0.0\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}\u0027\nvariants:\n pipeline:\n instructions: |\n Apply the repository-status capability for the payments service and emit only\n the strict output object. Mark any unavailable financial-control signal as an\n explicit null; do not report the service healthy when one is missing.\n manual:\n instructions: |\n Apply the repository-status capability for the payments service, then include\n payment-contract compatibility, reconciliation checks, and rollback readiness,\n surfacing uncertainty conversationally. Do not report the service healthy when\n any required financial-control signal is unavailable.\n a2a:\n instructions: |\n Apply the repository-status capability for the payments service and return\n attributed evidence for the calling agent \u2014 contract compatibility,\n reconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\n report the service healthy when any required financial-control signal is\n unavailable.\n---\n" }, { - "path": "skills/prepare-brief.skill.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: skill\nid: helio/skills/prepare-brief@1.0.0\nbinds: helio/capabilities/prepare-brief@1.0.0\ndisplayName: Prepare Brief\ndescription: Assemble an executive brief, requesting repository evidence when needed.\ncontextFiles:\n - context/executive-rhythm.md\ninstructions: |\n Build a decision-oriented brief from the supplied topic and evidence.\n When repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022topic\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022topic\u0022],\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022brief\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022openQuestions\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022brief\u0022,\u0022openQuestions\u0022]}\u0027\n" + "path": "skills/prepare-brief.skill.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: skill\nid: helio/skills/prepare-brief@1.0.0\nbinds: helio/capabilities/prepare-brief@1.0.0\ndisplayName: Prepare Brief\ndescription: Assemble an executive brief, requesting repository evidence when needed.\ncontextFiles:\n - context/executive-rhythm.md\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022topic\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022topic\u0022],\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022brief\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022openQuestions\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022brief\u0022,\u0022openQuestions\u0022]}\u0027\n---\nBuild a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n" }, { - "path": "skills/repository-status.skill.yaml", - "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: skill\nid: helio/skills/repository-status@1.0.0\nbinds: helio/capabilities/repository-status@1.0.0\ndisplayName: Repository Status\ndescription: Summarize repository health using current version-control and test evidence.\ninstructions: |\n Inspect the requested repository signals. Report branch state, recent material changes, test evidence, risks, and the next accountable action.\n Mark any unavailable signal explicitly instead of inferring success.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}\u0027\n" + "path": "skills/repository-status.skill.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: skill\nid: helio/skills/repository-status@1.0.0\nbinds: helio/capabilities/repository-status@1.0.0\ndisplayName: Repository Status\ndescription: Summarize repository health using current version-control and test evidence.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}\u0027\n---\nInspect the requested repository signals. Report branch state, recent material changes, test evidence, risks, and the next accountable action.\nMark any unavailable signal explicitly instead of inferring success.\n" }, { - "path": "skills/triage-message.skill.yaml", + "path": "skills/triage-message.skill.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: skill\nid: helio/skills/triage-message@1.0.0\nbinds: helio/capabilities/triage-message@1.0.0\ndisplayName: Triage Message\ndescription: Classify an inbound message and recommend an accountable next action.\nrequiresContextTypes:\n - helio/context-types/safety-policy@1.0.0\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022message\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022message\u0022],\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022summary\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022urgency\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022owner\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022nextAction\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022summary\u0022,\u0022urgency\u0022,\u0022owner\u0022,\u0022nextAction\u0022]}\u0027\n---\nRead the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n" + }, + { + "path": "tools/repository-signals.tool.tfer", + "mediaType": "text/plain", + "content": "---\nschemaVersion: 3\nkind: tool\nid: helio/tools/repository-signals@1.0.0\ndisplayName: Repository Signals\ndescription: \u003E-\n Reads version-control and test signals for a repository at runtime. TypeFerence\n declares this extern; the deployer implements the body (ADR-0017, Door B).\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022repository\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022repository\u0022],\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022branch\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022tests\u0022:{\u0022type\u0022:\u0022string\u0022}}}\u0027\n---\n" + }, + { + "path": "typeference.yaml", "mediaType": "application/yaml", - "content": "schemaVersion: 3\nkind: skill\nid: helio/skills/triage-message@1.0.0\nbinds: helio/capabilities/triage-message@1.0.0\ndisplayName: Triage Message\ndescription: Classify an inbound message and recommend an accountable next action.\ninstructions: |\n Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\n Separate facts from assumptions. Return a concise recommendation; do not send a reply.\ninputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022message\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022message\u0022],\u0022additionalProperties\u0022:false}\u0027\noutputSchema: \u0027{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022summary\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022urgency\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022owner\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022nextAction\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022summary\u0022,\u0022urgency\u0022,\u0022owner\u0022,\u0022nextAction\u0022]}\u0027\n" + "content": "schemaVersion: 1\nname: helio\nversion: 1.0.0\npublisher: helio.example\n" } ] }, @@ -140,33 +155,33 @@ { "path": "AGENTS.md", "mediaType": "text/markdown", - "content": "# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\n" + "content": "# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\n" }, { "path": "bundle.json", "mediaType": "application/json", - "content": "{\n \u0022id\u0022: \u0022helio/executive-assistant@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Executive Assistant\u0022,\n \u0022description\u0022: \u0022Coordinates an executive\\u0027s correspondence, briefings, and cross-agent requests.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/person-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/correspondent@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022principal\u0022: \u0022context/principal.md\u0022,\n \u0022safetyPolicy\u0022: \u0022context/safety-policy.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Distinguish drafts from messages approved for delivery.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.prepare-brief\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022,\n \u0022description\u0022: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022,\n \u0022instructions\u0022: \u0022Build a decision-oriented brief from the supplied topic and evidence.\\nWhen repository status is material, request \\u0060payments-repo-agent.repository-status\\u0060 and incorporate its returned evidence with attribution.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022topic\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022topic\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022brief\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022openQuestions\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022brief\\u0022,\\u0022openQuestions\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.triage-message\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/triage-message@1.0.0\u0022,\n \u0022description\u0022: \u0022Classify an inbound message and recommend an accountable next action.\u0022,\n \u0022instructions\u0022: \u0022Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022message\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022message\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022summary\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022urgency\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022owner\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022nextAction\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022summary\\u0022,\\u0022urgency\\u0022,\\u0022owner\\u0022,\\u0022nextAction\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/triage-message@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n ]\n}\n" + "content": "{\n \u0022id\u0022: \u0022helio/executive-assistant@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Executive Assistant\u0022,\n \u0022description\u0022: \u0022Coordinates an executive\\u0027s correspondence, briefings, and cross-agent requests.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/person-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/correspondent@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022principal\u0022: \u0022context/principal.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Distinguish drafts from messages approved for delivery.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022context\u0022: [\n {\n \u0022id\u0022: \u0022helio/context/safety-policy@1.0.0\u0022,\n \u0022contextType\u0022: \u0022helio/context-types/safety-policy@1.0.0\u0022,\n \u0022content\u0022: \u0022# Safety policy\\n\\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\\n\u0022\n }\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.prepare-brief\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022,\n \u0022description\u0022: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022,\n \u0022instructions\u0022: \u0022Build a decision-oriented brief from the supplied topic and evidence.\\nWhen repository status is material, request \\u0060payments-repo-agent.repository-status\\u0060 and incorporate its returned evidence with attribution.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022topic\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022topic\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022brief\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022openQuestions\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022brief\\u0022,\\u0022openQuestions\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.triage-message\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/triage-message@1.0.0\u0022,\n \u0022description\u0022: \u0022Classify an inbound message and recommend an accountable next action.\u0022,\n \u0022instructions\u0022: \u0022Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022message\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022message\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022summary\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022urgency\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022owner\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022nextAction\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022summary\\u0022,\\u0022urgency\\u0022,\\u0022owner\\u0022,\\u0022nextAction\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/triage-message@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n ]\n}\n" }, { "path": "provenance.json", "mediaType": "application/json", - "content": "[\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n]\n" + "content": "[\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n]\n" }, { "path": "skills/prepare-brief/SKILL.md", "mediaType": "text/markdown", - "content": "---\nname: prepare-brief\ndescription: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022\n---\n\nBuild a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/safety-policy.md\u0060\n- \u0060context/principal.md\u0060\n- \u0060context/executive-rhythm.md\u0060\n" + "content": "---\nname: prepare-brief\ndescription: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022\n---\n\nBuild a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/principal.md\u0060\n- \u0060context/executive-rhythm.md\u0060\n" }, { "path": "skills/triage-message/SKILL.md", "mediaType": "text/markdown", - "content": "---\nname: triage-message\ndescription: \u0022Classify an inbound message and recommend an accountable next action.\u0022\n---\n\nRead the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/safety-policy.md\u0060\n- \u0060context/principal.md\u0060\n" + "content": "---\nname: triage-message\ndescription: \u0022Classify an inbound message and recommend an accountable next action.\u0022\n---\n\nRead the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/principal.md\u0060\n" } ] }, "metadata": { "generatedBy": "TypeFerence", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", "sourceIdentifier": "urn:air:helio.example:typeference:source:helio", "target": "neutral" }, @@ -177,7 +192,7 @@ { "relation": "derivedFrom", "sourceId": "urn:air:helio.example:typeference:source:helio", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26" + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" } ] } @@ -199,28 +214,43 @@ { "path": "AGENTS.md", "mediaType": "text/markdown", - "content": "# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\n" + "content": "# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\n" }, { "path": "bundle.json", "mediaType": "application/json", - "content": "{\n \u0022id\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Payments Repository Agent\u0022,\n \u0022description\u0022: \u0022Specializes repository assistance for the fictional payments service.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/repository-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/repository-observer@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022repository\u0022: \u0022context/repository.md\u0022,\n \u0022safetyPolicy\u0022: \u0022context/safety-policy.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Prefer evidence from tests, diffs, and version control over recollection.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022payments-repo-agent.repository-status\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022,\n \u0022description\u0022: \u0022Report payments-service health with contract and reconciliation evidence.\u0022,\n \u0022instructions\u0022: \u0022Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\\nDo not report the service healthy when any required financial-control signal is unavailable.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022focus\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022status\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022risks\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022status\\u0022,\\u0022evidence\\u0022,\\u0022risks\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n ]\n}\n" + "content": "{\n \u0022id\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Payments Repository Agent\u0022,\n \u0022description\u0022: \u0022Specializes repository assistance for the fictional payments service.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/repository-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/repository-observer@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022repository\u0022: \u0022context/repository.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Prefer evidence from tests, diffs, and version control over recollection.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022context\u0022: [\n {\n \u0022id\u0022: \u0022helio/context/safety-policy@1.0.0\u0022,\n \u0022contextType\u0022: \u0022helio/context-types/safety-policy@1.0.0\u0022,\n \u0022content\u0022: \u0022# Safety policy\\n\\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\\n\u0022\n }\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022payments-repo-agent.repository-status\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022,\n \u0022description\u0022: \u0022Report payments-service health with contract and reconciliation evidence.\u0022,\n \u0022instructions\u0022: \u0022Apply the repository-status capability for the payments service and emit only\\nthe strict output object. Mark any unavailable financial-control signal as an\\nexplicit null; do not report the service healthy when one is missing.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022focus\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022status\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022risks\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022status\\u0022,\\u0022evidence\\u0022,\\u0022risks\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022variants\u0022: {\n \u0022a2a\u0022: \u0022Apply the repository-status capability for the payments service and return\\nattributed evidence for the calling agent \\u2014 contract compatibility,\\nreconciliation, and rollback readiness \\u2014 with no user-facing framing. Do not\\nreport the service healthy when any required financial-control signal is\\nunavailable.\\n\u0022,\n \u0022manual\u0022: \u0022Apply the repository-status capability for the payments service, then include\\npayment-contract compatibility, reconciliation checks, and rollback readiness,\\nsurfacing uncertainty conversationally. Do not report the service healthy when\\nany required financial-control signal is unavailable.\\n\u0022,\n \u0022pipeline\u0022: \u0022Apply the repository-status capability for the payments service and emit only\\nthe strict output object. Mark any unavailable financial-control signal as an\\nexplicit null; do not report the service healthy when one is missing.\\n\u0022\n },\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n ]\n}\n" }, { "path": "provenance.json", "mediaType": "application/json", - "content": "[\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n]\n" + "content": "[\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n]\n" + }, + { + "path": "skills/repository-status/SKILL.a2a.md", + "mediaType": "text/markdown", + "content": "---\nname: repository-status\ndescription: \u0022Report payments-service health with contract and reconciliation evidence.\u0022\n---\n\nApply the repository-status capability for the payments service and return\nattributed evidence for the calling agent \u2014 contract compatibility,\nreconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\nreport the service healthy when any required financial-control signal is\nunavailable.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/repository.md\u0060\n- \u0060context/payments-service.md\u0060\n" + }, + { + "path": "skills/repository-status/SKILL.manual.md", + "mediaType": "text/markdown", + "content": "---\nname: repository-status\ndescription: \u0022Report payments-service health with contract and reconciliation evidence.\u0022\n---\n\nApply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/repository.md\u0060\n- \u0060context/payments-service.md\u0060\n" }, { "path": "skills/repository-status/SKILL.md", "mediaType": "text/markdown", - "content": "---\nname: repository-status\ndescription: \u0022Report payments-service health with contract and reconciliation evidence.\u0022\n---\n\nApply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\nDo not report the service healthy when any required financial-control signal is unavailable.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/safety-policy.md\u0060\n- \u0060context/repository.md\u0060\n- \u0060context/payments-service.md\u0060\n" + "content": "---\nname: repository-status\ndescription: \u0022Report payments-service health with contract and reconciliation evidence.\u0022\n---\n\nApply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/repository.md\u0060\n- \u0060context/payments-service.md\u0060\n" + }, + { + "path": "skills/repository-status/SKILL.pipeline.md", + "mediaType": "text/markdown", + "content": "---\nname: repository-status\ndescription: \u0022Report payments-service health with contract and reconciliation evidence.\u0022\n---\n\nApply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/repository.md\u0060\n- \u0060context/payments-service.md\u0060\n" } ] }, "metadata": { "generatedBy": "TypeFerence", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", "sourceIdentifier": "urn:air:helio.example:typeference:source:helio", "target": "neutral" }, @@ -231,7 +261,7 @@ { "relation": "derivedFrom", "sourceId": "urn:air:helio.example:typeference:source:helio", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26" + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" } ] } @@ -254,12 +284,12 @@ { "path": ".agents/skills/prepare-brief/SKILL.md", "mediaType": "text/markdown", - "content": "---\nname: prepare-brief\ndescription: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022\n---\n\nBuild a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/safety-policy.md\u0060\n- \u0060context/principal.md\u0060\n- \u0060context/executive-rhythm.md\u0060\n" + "content": "---\nname: prepare-brief\ndescription: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022\n---\n\nBuild a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/principal.md\u0060\n- \u0060context/executive-rhythm.md\u0060\n" }, { "path": ".agents/skills/triage-message/SKILL.md", "mediaType": "text/markdown", - "content": "---\nname: triage-message\ndescription: \u0022Classify an inbound message and recommend an accountable next action.\u0022\n---\n\nRead the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/safety-policy.md\u0060\n- \u0060context/principal.md\u0060\n" + "content": "---\nname: triage-message\ndescription: \u0022Classify an inbound message and recommend an accountable next action.\u0022\n---\n\nRead the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/principal.md\u0060\n" }, { "path": ".codex/config.toml", @@ -269,18 +299,18 @@ { "path": ".typeference/bundle.json", "mediaType": "application/json", - "content": "{\n \u0022id\u0022: \u0022helio/executive-assistant@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Executive Assistant\u0022,\n \u0022description\u0022: \u0022Coordinates an executive\\u0027s correspondence, briefings, and cross-agent requests.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/person-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/correspondent@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022principal\u0022: \u0022context/principal.md\u0022,\n \u0022safetyPolicy\u0022: \u0022context/safety-policy.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Distinguish drafts from messages approved for delivery.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.prepare-brief\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022,\n \u0022description\u0022: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022,\n \u0022instructions\u0022: \u0022Build a decision-oriented brief from the supplied topic and evidence.\\nWhen repository status is material, request \\u0060payments-repo-agent.repository-status\\u0060 and incorporate its returned evidence with attribution.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022topic\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022topic\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022brief\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022openQuestions\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022brief\\u0022,\\u0022openQuestions\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.triage-message\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/triage-message@1.0.0\u0022,\n \u0022description\u0022: \u0022Classify an inbound message and recommend an accountable next action.\u0022,\n \u0022instructions\u0022: \u0022Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022message\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022message\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022summary\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022urgency\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022owner\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022nextAction\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022summary\\u0022,\\u0022urgency\\u0022,\\u0022owner\\u0022,\\u0022nextAction\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/triage-message@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n ]\n}\n" + "content": "{\n \u0022id\u0022: \u0022helio/executive-assistant@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Executive Assistant\u0022,\n \u0022description\u0022: \u0022Coordinates an executive\\u0027s correspondence, briefings, and cross-agent requests.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/person-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/correspondent@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022principal\u0022: \u0022context/principal.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Distinguish drafts from messages approved for delivery.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022context\u0022: [\n {\n \u0022id\u0022: \u0022helio/context/safety-policy@1.0.0\u0022,\n \u0022contextType\u0022: \u0022helio/context-types/safety-policy@1.0.0\u0022,\n \u0022content\u0022: \u0022# Safety policy\\n\\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\\n\u0022\n }\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.prepare-brief\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022,\n \u0022description\u0022: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022,\n \u0022instructions\u0022: \u0022Build a decision-oriented brief from the supplied topic and evidence.\\nWhen repository status is material, request \\u0060payments-repo-agent.repository-status\\u0060 and incorporate its returned evidence with attribution.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022topic\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022topic\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022brief\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022openQuestions\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022brief\\u0022,\\u0022openQuestions\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.triage-message\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/triage-message@1.0.0\u0022,\n \u0022description\u0022: \u0022Classify an inbound message and recommend an accountable next action.\u0022,\n \u0022instructions\u0022: \u0022Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022message\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022message\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022summary\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022urgency\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022owner\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022nextAction\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022summary\\u0022,\\u0022urgency\\u0022,\\u0022owner\\u0022,\\u0022nextAction\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/triage-message@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n ]\n}\n" }, { "path": "AGENTS.md", "mediaType": "text/markdown", - "content": "# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\n" + "content": "# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\n" } ] }, "metadata": { "generatedBy": "TypeFerence", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", "sourceIdentifier": "urn:air:helio.example:typeference:source:helio", "target": "codex" }, @@ -291,7 +321,7 @@ { "relation": "derivedFrom", "sourceId": "urn:air:helio.example:typeference:source:helio", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26" + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" } ] } @@ -313,7 +343,7 @@ { "path": ".agents/skills/repository-status/SKILL.md", "mediaType": "text/markdown", - "content": "---\nname: repository-status\ndescription: \u0022Report payments-service health with contract and reconciliation evidence.\u0022\n---\n\nApply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\nDo not report the service healthy when any required financial-control signal is unavailable.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/safety-policy.md\u0060\n- \u0060context/repository.md\u0060\n- \u0060context/payments-service.md\u0060\n" + "content": "---\nname: repository-status\ndescription: \u0022Report payments-service health with contract and reconciliation evidence.\u0022\n---\n\nApply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n\n## Context loaded on invocation\n\n- \u0060context/organization.md\u0060\n- \u0060context/repository.md\u0060\n- \u0060context/payments-service.md\u0060\n" }, { "path": ".codex/config.toml", @@ -323,18 +353,18 @@ { "path": ".typeference/bundle.json", "mediaType": "application/json", - "content": "{\n \u0022id\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Payments Repository Agent\u0022,\n \u0022description\u0022: \u0022Specializes repository assistance for the fictional payments service.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/repository-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/repository-observer@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022repository\u0022: \u0022context/repository.md\u0022,\n \u0022safetyPolicy\u0022: \u0022context/safety-policy.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Prefer evidence from tests, diffs, and version control over recollection.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022payments-repo-agent.repository-status\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022,\n \u0022description\u0022: \u0022Report payments-service health with contract and reconciliation evidence.\u0022,\n \u0022instructions\u0022: \u0022Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\\nDo not report the service healthy when any required financial-control signal is unavailable.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022focus\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022status\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022risks\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022status\\u0022,\\u0022evidence\\u0022,\\u0022risks\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n ]\n}\n" + "content": "{\n \u0022id\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Payments Repository Agent\u0022,\n \u0022description\u0022: \u0022Specializes repository assistance for the fictional payments service.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/repository-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/repository-observer@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022repository\u0022: \u0022context/repository.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Prefer evidence from tests, diffs, and version control over recollection.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022context\u0022: [\n {\n \u0022id\u0022: \u0022helio/context/safety-policy@1.0.0\u0022,\n \u0022contextType\u0022: \u0022helio/context-types/safety-policy@1.0.0\u0022,\n \u0022content\u0022: \u0022# Safety policy\\n\\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\\n\u0022\n }\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022payments-repo-agent.repository-status\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022,\n \u0022description\u0022: \u0022Report payments-service health with contract and reconciliation evidence.\u0022,\n \u0022instructions\u0022: \u0022Apply the repository-status capability for the payments service and emit only\\nthe strict output object. Mark any unavailable financial-control signal as an\\nexplicit null; do not report the service healthy when one is missing.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022focus\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022status\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022risks\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022status\\u0022,\\u0022evidence\\u0022,\\u0022risks\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022variants\u0022: {\n \u0022a2a\u0022: \u0022Apply the repository-status capability for the payments service and return\\nattributed evidence for the calling agent \\u2014 contract compatibility,\\nreconciliation, and rollback readiness \\u2014 with no user-facing framing. Do not\\nreport the service healthy when any required financial-control signal is\\nunavailable.\\n\u0022,\n \u0022manual\u0022: \u0022Apply the repository-status capability for the payments service, then include\\npayment-contract compatibility, reconciliation checks, and rollback readiness,\\nsurfacing uncertainty conversationally. Do not report the service healthy when\\nany required financial-control signal is unavailable.\\n\u0022,\n \u0022pipeline\u0022: \u0022Apply the repository-status capability for the payments service and emit only\\nthe strict output object. Mark any unavailable financial-control signal as an\\nexplicit null; do not report the service healthy when one is missing.\\n\u0022\n },\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n ]\n}\n" }, { "path": "AGENTS.md", "mediaType": "text/markdown", - "content": "# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\n" + "content": "# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\n" } ] }, "metadata": { "generatedBy": "TypeFerence", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", "sourceIdentifier": "urn:air:helio.example:typeference:source:helio", "target": "codex" }, @@ -345,7 +375,7 @@ { "relation": "derivedFrom", "sourceId": "urn:air:helio.example:typeference:source:helio", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26" + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" } ] } @@ -368,23 +398,23 @@ { "path": ".github/agents/executive-assistant.agent.md", "mediaType": "text/markdown", - "content": "---\nname: executive-assistant\ndescription: \u0022Coordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\u0022\n---\n\n# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\n" + "content": "---\nname: executive-assistant\ndescription: \u0022Coordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\u0022\n---\n\n# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n\nBuild a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\nRead the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n\n\n" }, { "path": ".github/copilot-instructions.md", "mediaType": "text/markdown", - "content": "# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\n" + "content": "# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n\nBuild a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\nRead the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n\n\n" }, { "path": ".typeference/bundle.json", "mediaType": "application/json", - "content": "{\n \u0022id\u0022: \u0022helio/executive-assistant@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Executive Assistant\u0022,\n \u0022description\u0022: \u0022Coordinates an executive\\u0027s correspondence, briefings, and cross-agent requests.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/person-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/correspondent@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022principal\u0022: \u0022context/principal.md\u0022,\n \u0022safetyPolicy\u0022: \u0022context/safety-policy.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Distinguish drafts from messages approved for delivery.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.prepare-brief\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022,\n \u0022description\u0022: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022,\n \u0022instructions\u0022: \u0022Build a decision-oriented brief from the supplied topic and evidence.\\nWhen repository status is material, request \\u0060payments-repo-agent.repository-status\\u0060 and incorporate its returned evidence with attribution.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022topic\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022topic\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022brief\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022openQuestions\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022brief\\u0022,\\u0022openQuestions\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.triage-message\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/triage-message@1.0.0\u0022,\n \u0022description\u0022: \u0022Classify an inbound message and recommend an accountable next action.\u0022,\n \u0022instructions\u0022: \u0022Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022message\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022message\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022summary\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022urgency\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022owner\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022nextAction\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022summary\\u0022,\\u0022urgency\\u0022,\\u0022owner\\u0022,\\u0022nextAction\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/triage-message@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n ]\n}\n" + "content": "{\n \u0022id\u0022: \u0022helio/executive-assistant@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Executive Assistant\u0022,\n \u0022description\u0022: \u0022Coordinates an executive\\u0027s correspondence, briefings, and cross-agent requests.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/person-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/correspondent@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022principal\u0022: \u0022context/principal.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Distinguish drafts from messages approved for delivery.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022context\u0022: [\n {\n \u0022id\u0022: \u0022helio/context/safety-policy@1.0.0\u0022,\n \u0022contextType\u0022: \u0022helio/context-types/safety-policy@1.0.0\u0022,\n \u0022content\u0022: \u0022# Safety policy\\n\\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\\n\u0022\n }\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.prepare-brief\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022,\n \u0022description\u0022: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022,\n \u0022instructions\u0022: \u0022Build a decision-oriented brief from the supplied topic and evidence.\\nWhen repository status is material, request \\u0060payments-repo-agent.repository-status\\u0060 and incorporate its returned evidence with attribution.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022topic\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022topic\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022brief\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022openQuestions\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022brief\\u0022,\\u0022openQuestions\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.triage-message\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/triage-message@1.0.0\u0022,\n \u0022description\u0022: \u0022Classify an inbound message and recommend an accountable next action.\u0022,\n \u0022instructions\u0022: \u0022Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022message\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022message\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022summary\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022urgency\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022owner\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022nextAction\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022summary\\u0022,\\u0022urgency\\u0022,\\u0022owner\\u0022,\\u0022nextAction\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/triage-message@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n ]\n}\n" } ] }, "metadata": { "generatedBy": "TypeFerence", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", "sourceIdentifier": "urn:air:helio.example:typeference:source:helio", "target": "copilot" }, @@ -395,7 +425,7 @@ { "relation": "derivedFrom", "sourceId": "urn:air:helio.example:typeference:source:helio", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26" + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" } ] } @@ -417,23 +447,23 @@ { "path": ".github/agents/payments-repo-agent.agent.md", "mediaType": "text/markdown", - "content": "---\nname: payments-repo-agent\ndescription: \u0022Specializes repository assistance for the fictional payments service.\u0022\n---\n\n# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\n" + "content": "---\nname: payments-repo-agent\ndescription: \u0022Specializes repository assistance for the fictional payments service.\u0022\n---\n\n# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\nApply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n\n\n" }, { "path": ".github/copilot-instructions.md", "mediaType": "text/markdown", - "content": "# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\n" + "content": "# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\nApply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n\n\n" }, { "path": ".typeference/bundle.json", "mediaType": "application/json", - "content": "{\n \u0022id\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Payments Repository Agent\u0022,\n \u0022description\u0022: \u0022Specializes repository assistance for the fictional payments service.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/repository-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/repository-observer@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022repository\u0022: \u0022context/repository.md\u0022,\n \u0022safetyPolicy\u0022: \u0022context/safety-policy.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Prefer evidence from tests, diffs, and version control over recollection.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022payments-repo-agent.repository-status\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022,\n \u0022description\u0022: \u0022Report payments-service health with contract and reconciliation evidence.\u0022,\n \u0022instructions\u0022: \u0022Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\\nDo not report the service healthy when any required financial-control signal is unavailable.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022focus\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022status\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022risks\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022status\\u0022,\\u0022evidence\\u0022,\\u0022risks\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n ]\n}\n" + "content": "{\n \u0022id\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Payments Repository Agent\u0022,\n \u0022description\u0022: \u0022Specializes repository assistance for the fictional payments service.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/repository-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/repository-observer@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022repository\u0022: \u0022context/repository.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Prefer evidence from tests, diffs, and version control over recollection.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022context\u0022: [\n {\n \u0022id\u0022: \u0022helio/context/safety-policy@1.0.0\u0022,\n \u0022contextType\u0022: \u0022helio/context-types/safety-policy@1.0.0\u0022,\n \u0022content\u0022: \u0022# Safety policy\\n\\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\\n\u0022\n }\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022payments-repo-agent.repository-status\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022,\n \u0022description\u0022: \u0022Report payments-service health with contract and reconciliation evidence.\u0022,\n \u0022instructions\u0022: \u0022Apply the repository-status capability for the payments service and emit only\\nthe strict output object. Mark any unavailable financial-control signal as an\\nexplicit null; do not report the service healthy when one is missing.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022focus\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022status\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022risks\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022status\\u0022,\\u0022evidence\\u0022,\\u0022risks\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022variants\u0022: {\n \u0022a2a\u0022: \u0022Apply the repository-status capability for the payments service and return\\nattributed evidence for the calling agent \\u2014 contract compatibility,\\nreconciliation, and rollback readiness \\u2014 with no user-facing framing. Do not\\nreport the service healthy when any required financial-control signal is\\nunavailable.\\n\u0022,\n \u0022manual\u0022: \u0022Apply the repository-status capability for the payments service, then include\\npayment-contract compatibility, reconciliation checks, and rollback readiness,\\nsurfacing uncertainty conversationally. Do not report the service healthy when\\nany required financial-control signal is unavailable.\\n\u0022,\n \u0022pipeline\u0022: \u0022Apply the repository-status capability for the payments service and emit only\\nthe strict output object. Mark any unavailable financial-control signal as an\\nexplicit null; do not report the service healthy when one is missing.\\n\u0022\n },\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n ]\n}\n" } ] }, "metadata": { "generatedBy": "TypeFerence", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", "sourceIdentifier": "urn:air:helio.example:typeference:source:helio", "target": "copilot" }, @@ -444,7 +474,7 @@ { "relation": "derivedFrom", "sourceId": "urn:air:helio.example:typeference:source:helio", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26" + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" } ] } @@ -467,23 +497,23 @@ { "path": ".cursor/rules/executive-assistant.mdc", "mediaType": "text/markdown", - "content": "---\ndescription: \u0022Coordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\u0022\nglobs:\nalwaysApply: true\n---\n\n# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\n" + "content": "---\ndescription: \u0022Coordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\u0022\nglobs:\nalwaysApply: true\n---\n\n# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n\nBuild a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\nRead the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n\n\n" }, { "path": ".typeference/bundle.json", "mediaType": "application/json", - "content": "{\n \u0022id\u0022: \u0022helio/executive-assistant@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Executive Assistant\u0022,\n \u0022description\u0022: \u0022Coordinates an executive\\u0027s correspondence, briefings, and cross-agent requests.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/person-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/correspondent@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022principal\u0022: \u0022context/principal.md\u0022,\n \u0022safetyPolicy\u0022: \u0022context/safety-policy.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Distinguish drafts from messages approved for delivery.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.prepare-brief\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022,\n \u0022description\u0022: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022,\n \u0022instructions\u0022: \u0022Build a decision-oriented brief from the supplied topic and evidence.\\nWhen repository status is material, request \\u0060payments-repo-agent.repository-status\\u0060 and incorporate its returned evidence with attribution.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022topic\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022topic\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022brief\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022openQuestions\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022brief\\u0022,\\u0022openQuestions\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.triage-message\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/triage-message@1.0.0\u0022,\n \u0022description\u0022: \u0022Classify an inbound message and recommend an accountable next action.\u0022,\n \u0022instructions\u0022: \u0022Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022message\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022message\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022summary\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022urgency\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022owner\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022nextAction\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022summary\\u0022,\\u0022urgency\\u0022,\\u0022owner\\u0022,\\u0022nextAction\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/principal.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/triage-message@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n ]\n}\n" + "content": "{\n \u0022id\u0022: \u0022helio/executive-assistant@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Executive Assistant\u0022,\n \u0022description\u0022: \u0022Coordinates an executive\\u0027s correspondence, briefings, and cross-agent requests.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/person-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/correspondent@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022principal\u0022: \u0022context/principal.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Distinguish drafts from messages approved for delivery.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022context\u0022: [\n {\n \u0022id\u0022: \u0022helio/context/safety-policy@1.0.0\u0022,\n \u0022contextType\u0022: \u0022helio/context-types/safety-policy@1.0.0\u0022,\n \u0022content\u0022: \u0022# Safety policy\\n\\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\\n\u0022\n }\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.prepare-brief\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022,\n \u0022description\u0022: \u0022Assemble an executive brief, requesting repository evidence when needed.\u0022,\n \u0022instructions\u0022: \u0022Build a decision-oriented brief from the supplied topic and evidence.\\nWhen repository status is material, request \\u0060payments-repo-agent.repository-status\\u0060 and incorporate its returned evidence with attribution.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022topic\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022topic\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022brief\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022openQuestions\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022brief\\u0022,\\u0022openQuestions\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022,\n \u0022context/executive-rhythm.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/prepare-brief@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/prepare-brief@1.0.0\u0022\n }\n ]\n },\n {\n \u0022dispatchName\u0022: \u0022executive-assistant.triage-message\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/triage-message@1.0.0\u0022,\n \u0022description\u0022: \u0022Classify an inbound message and recommend an accountable next action.\u0022,\n \u0022instructions\u0022: \u0022Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022message\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022message\\u0022],\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022summary\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022urgency\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022owner\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022nextAction\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022required\\u0022:[\\u0022summary\\u0022,\\u0022urgency\\u0022,\\u0022owner\\u0022,\\u0022nextAction\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/principal.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/triage-message@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/triage-message@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.principal\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/person-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/person-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/correspondent@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/executive-assistant@1.0.0\u0022\n }\n ]\n}\n" }, { "path": "AGENTS.md", "mediaType": "text/markdown", - "content": "# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\n" + "content": "# Helio Executive Assistant\n\nCoordinates an executive\u0027s correspondence, briefings, and cross-agent requests.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Distinguish drafts from messages approved for delivery.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060principal\u0060: \u0060context/principal.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060executive-assistant.prepare-brief\u0060: Assemble an executive brief, requesting repository evidence when needed.\n\nBuild a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n\n- \u0060executive-assistant.triage-message\u0060: Classify an inbound message and recommend an accountable next action.\n\nRead the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n\n\n" } ] }, "metadata": { "generatedBy": "TypeFerence", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", "sourceIdentifier": "urn:air:helio.example:typeference:source:helio", "target": "cursor" }, @@ -494,7 +524,7 @@ { "relation": "derivedFrom", "sourceId": "urn:air:helio.example:typeference:source:helio", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26" + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" } ] } @@ -516,23 +546,23 @@ { "path": ".cursor/rules/payments-repo-agent.mdc", "mediaType": "text/markdown", - "content": "---\ndescription: \u0022Specializes repository assistance for the fictional payments service.\u0022\nglobs:\nalwaysApply: true\n---\n\n# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\n" + "content": "---\ndescription: \u0022Specializes repository assistance for the fictional payments service.\u0022\nglobs:\nalwaysApply: true\n---\n\n# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\nApply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n\n\n" }, { "path": ".typeference/bundle.json", "mediaType": "application/json", - "content": "{\n \u0022id\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Payments Repository Agent\u0022,\n \u0022description\u0022: \u0022Specializes repository assistance for the fictional payments service.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/repository-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/repository-observer@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022repository\u0022: \u0022context/repository.md\u0022,\n \u0022safetyPolicy\u0022: \u0022context/safety-policy.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Prefer evidence from tests, diffs, and version control over recollection.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022payments-repo-agent.repository-status\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022,\n \u0022description\u0022: \u0022Report payments-service health with contract and reconciliation evidence.\u0022,\n \u0022instructions\u0022: \u0022Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\\nDo not report the service healthy when any required financial-control signal is unavailable.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022focus\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022status\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022risks\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022status\\u0022,\\u0022evidence\\u0022,\\u0022risks\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/safety-policy.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.safetyPolicy\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n ]\n}\n" + "content": "{\n \u0022id\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022,\n \u0022displayName\u0022: \u0022Helio Payments Repository Agent\u0022,\n \u0022description\u0022: \u0022Specializes repository assistance for the fictional payments service.\u0022,\n \u0022emit\u0022: true,\n \u0022embeds\u0022: [\n \u0022helio/profiles/repository-defaults@1.0.0\u0022\n ],\n \u0022satisfies\u0022: [\n \u0022helio/interfaces/repository-observer@1.0.0\u0022\n ],\n \u0022slots\u0022: {\n \u0022organization\u0022: \u0022context/organization.md\u0022,\n \u0022repository\u0022: \u0022context/repository.md\u0022\n },\n \u0022workingNorms\u0022: [\n \u0022Preserve a clear audit trail for material decisions.\u0022,\n \u0022State uncertainty and route work to an accountable owner when authority is unclear.\u0022,\n \u0022Prefer evidence from tests, diffs, and version control over recollection.\u0022\n ],\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022context\u0022: [\n {\n \u0022id\u0022: \u0022helio/context/safety-policy@1.0.0\u0022,\n \u0022contextType\u0022: \u0022helio/context-types/safety-policy@1.0.0\u0022,\n \u0022content\u0022: \u0022# Safety policy\\n\\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\\n\u0022\n }\n ],\n \u0022skills\u0022: [\n {\n \u0022dispatchName\u0022: \u0022payments-repo-agent.repository-status\u0022,\n \u0022capabilityId\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022,\n \u0022implementationId\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022,\n \u0022description\u0022: \u0022Report payments-service health with contract and reconciliation evidence.\u0022,\n \u0022instructions\u0022: \u0022Apply the repository-status capability for the payments service and emit only\\nthe strict output object. Mark any unavailable financial-control signal as an\\nexplicit null; do not report the service healthy when one is missing.\\n\u0022,\n \u0022inputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022focus\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022additionalProperties\\u0022:false}\u0022,\n \u0022outputSchema\u0022: \u0022{\\u0022type\\u0022:\\u0022object\\u0022,\\u0022properties\\u0022:{\\u0022status\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022},\\u0022evidence\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}},\\u0022risks\\u0022:{\\u0022type\\u0022:\\u0022array\\u0022,\\u0022items\\u0022:{\\u0022type\\u0022:\\u0022string\\u0022}}},\\u0022required\\u0022:[\\u0022status\\u0022,\\u0022evidence\\u0022,\\u0022risks\\u0022]}\u0022,\n \u0022contextFiles\u0022: [\n \u0022context/organization.md\u0022,\n \u0022context/repository.md\u0022,\n \u0022context/payments-service.md\u0022\n ],\n \u0022variants\u0022: {\n \u0022a2a\u0022: \u0022Apply the repository-status capability for the payments service and return\\nattributed evidence for the calling agent \\u2014 contract compatibility,\\nreconciliation, and rollback readiness \\u2014 with no user-facing framing. Do not\\nreport the service healthy when any required financial-control signal is\\nunavailable.\\n\u0022,\n \u0022manual\u0022: \u0022Apply the repository-status capability for the payments service, then include\\npayment-contract compatibility, reconciliation checks, and rollback readiness,\\nsurfacing uncertainty conversationally. Do not report the service healthy when\\nany required financial-control signal is unavailable.\\n\u0022,\n \u0022pipeline\u0022: \u0022Apply the repository-status capability for the payments service and emit only\\nthe strict output object. Mark any unavailable financial-control signal as an\\nexplicit null; do not report the service healthy when one is missing.\\n\u0022\n },\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022skill.capability\u0022,\n \u0022source\u0022: \u0022helio/capabilities/repository-status@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022skill.implementation\u0022,\n \u0022source\u0022: \u0022helio/skills/payments-repository-status@1.0.0\u0022\n }\n ]\n }\n ],\n \u0022provenance\u0022: [\n {\n \u0022field\u0022: \u0022slots.organization\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/enterprise-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/enterprise-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022slots.repository\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022workingNorms\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/profiles/repository-defaults@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022embeds.helio/profiles/repository-defaults@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022displayName\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022description\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022contextFiles\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n },\n {\n \u0022field\u0022: \u0022satisfies.helio/interfaces/repository-observer@1.0.0\u0022,\n \u0022source\u0022: \u0022helio/payments-repo-agent@1.0.0\u0022\n }\n ]\n}\n" }, { "path": "AGENTS.md", "mediaType": "text/markdown", - "content": "# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n- \u0060safetyPolicy\u0060: \u0060context/safety-policy.md\u0060\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\n" + "content": "# Helio Payments Repository Agent\n\nSpecializes repository assistance for the fictional payments service.\n\n## Working norms\n\n- Preserve a clear audit trail for material decisions.\n- State uncertainty and route work to an accountable owner when authority is unclear.\n- Prefer evidence from tests, diffs, and version control over recollection.\n\n## Context slots\n\n- \u0060organization\u0060: \u0060context/organization.md\u0060\n- \u0060repository\u0060: \u0060context/repository.md\u0060\n\n## Context\n\n### Helio Safety Policy\n\n# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n\n## Available skills\n\n- \u0060payments-repo-agent.repository-status\u0060: Report payments-service health with contract and reconciliation evidence.\n\nApply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n\n\n" } ] }, "metadata": { "generatedBy": "TypeFerence", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", "sourceIdentifier": "urn:air:helio.example:typeference:source:helio", "target": "cursor" }, @@ -543,7 +573,347 @@ { "relation": "derivedFrom", "sourceId": "urn:air:helio.example:typeference:source:helio", - "sourceDigest": "sha256:70270ce0b55dd8c489246a0cffcd64b76f87b4f99e2815ce471ffc7c7dc79d26" + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" + } + ] + } + }, + { + "identifier": "urn:air:helio.example:typeference:a2a:executive-assistant", + "displayName": "Helio Executive Assistant (A2A agent card)", + "type": "application/a2a-agent-card\u002Bjson", + "description": "A2A Agent Card for Helio Executive Assistant.", + "capabilities": [ + "executive-assistant.prepare-brief", + "executive-assistant.triage-message" + ], + "version": "1.0.0", + "data": { + "protocolVersion": "0.3.0", + "name": "Helio Executive Assistant", + "description": "Coordinates an executive\u0027s correspondence, briefings, and cross-agent requests.", + "version": "1.0.0", + "url": "https://helio.example/a2a/executive-assistant", + "preferredTransport": "JSONRPC", + "capabilities": { + "streaming": false, + "pushNotifications": false + }, + "defaultInputModes": [ + "application/json" + ], + "defaultOutputModes": [ + "application/json" + ], + "provider": { + "organization": "helio.example", + "url": "https://helio.example" + }, + "skills": [ + { + "id": "executive-assistant.prepare-brief", + "name": "prepare-brief", + "description": "Assemble an executive brief, requesting repository evidence when needed.", + "tags": [ + "typeference" + ] + }, + { + "id": "executive-assistant.triage-message", + "name": "triage-message", + "description": "Classify an inbound message and recommend an accountable next action.", + "tags": [ + "typeference" + ] + } + ] + }, + "metadata": { + "generatedBy": "TypeFerence", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", + "sourceIdentifier": "urn:air:helio.example:typeference:source:helio" + }, + "trustManifest": { + "identity": "https://helio.example", + "identityType": "https", + "provenance": [ + { + "relation": "derivedFrom", + "sourceId": "urn:air:helio.example:typeference:source:helio", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" + } + ] + } + }, + { + "identifier": "urn:air:helio.example:typeference:mcp:executive-assistant", + "displayName": "Helio Executive Assistant (MCP tools)", + "type": "application/mcp-server\u002Bjson", + "description": "MCP tool manifest for Helio Executive Assistant.", + "capabilities": [ + "executive-assistant.prepare-brief", + "executive-assistant.triage-message" + ], + "version": "1.0.0", + "data": { + "tools": [ + { + "name": "executive-assistant.prepare-brief", + "description": "Assemble an executive brief, requesting repository evidence when needed.", + "inputSchema": { + "type": "object", + "properties": { + "topic": { + "type": "string" + }, + "evidence": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "topic" + ], + "additionalProperties": false + }, + "outputSchema": { + "type": "object", + "properties": { + "brief": { + "type": "string" + }, + "openQuestions": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "brief", + "openQuestions" + ] + }, + "x-typeference": { + "instructionsTemplate": "Build a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n", + "requiresTools": [] + } + }, + { + "name": "executive-assistant.triage-message", + "description": "Classify an inbound message and recommend an accountable next action.", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + }, + "outputSchema": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "urgency": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "nextAction": { + "type": "string" + } + }, + "required": [ + "summary", + "urgency", + "owner", + "nextAction" + ] + }, + "x-typeference": { + "instructionsTemplate": "Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n", + "requiresTools": [] + } + } + ], + "x-typeference": { + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ] + } + }, + "metadata": { + "generatedBy": "TypeFerence", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", + "sourceIdentifier": "urn:air:helio.example:typeference:source:helio" + }, + "trustManifest": { + "identity": "https://helio.example", + "identityType": "https", + "provenance": [ + { + "relation": "derivedFrom", + "sourceId": "urn:air:helio.example:typeference:source:helio", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" + } + ] + } + }, + { + "identifier": "urn:air:helio.example:typeference:a2a:payments-repo-agent", + "displayName": "Helio Payments Repository Agent (A2A agent card)", + "type": "application/a2a-agent-card\u002Bjson", + "description": "A2A Agent Card for Helio Payments Repository Agent.", + "capabilities": [ + "payments-repo-agent.repository-status" + ], + "version": "1.0.0", + "data": { + "protocolVersion": "0.3.0", + "name": "Helio Payments Repository Agent", + "description": "Specializes repository assistance for the fictional payments service.", + "version": "1.0.0", + "url": "https://helio.example/a2a/payments-repo-agent", + "preferredTransport": "JSONRPC", + "capabilities": { + "streaming": false, + "pushNotifications": false + }, + "defaultInputModes": [ + "application/json" + ], + "defaultOutputModes": [ + "application/json" + ], + "provider": { + "organization": "helio.example", + "url": "https://helio.example" + }, + "skills": [ + { + "id": "payments-repo-agent.repository-status", + "name": "repository-status", + "description": "Report payments-service health with contract and reconciliation evidence.", + "tags": [ + "typeference" + ] + } + ] + }, + "metadata": { + "generatedBy": "TypeFerence", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", + "sourceIdentifier": "urn:air:helio.example:typeference:source:helio" + }, + "trustManifest": { + "identity": "https://helio.example", + "identityType": "https", + "provenance": [ + { + "relation": "derivedFrom", + "sourceId": "urn:air:helio.example:typeference:source:helio", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" + } + ] + } + }, + { + "identifier": "urn:air:helio.example:typeference:mcp:payments-repo-agent", + "displayName": "Helio Payments Repository Agent (MCP tools)", + "type": "application/mcp-server\u002Bjson", + "description": "MCP tool manifest for Helio Payments Repository Agent.", + "capabilities": [ + "payments-repo-agent.repository-status" + ], + "version": "1.0.0", + "data": { + "tools": [ + { + "name": "payments-repo-agent.repository-status", + "description": "Report payments-service health with contract and reconciliation evidence.", + "inputSchema": { + "type": "object", + "properties": { + "focus": { + "type": "string" + } + }, + "additionalProperties": false + }, + "outputSchema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "evidence": { + "type": "array", + "items": { + "type": "string" + } + }, + "risks": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "status", + "evidence", + "risks" + ] + }, + "x-typeference": { + "instructionsTemplate": "Apply the repository-status capability for the payments service and return\nattributed evidence for the calling agent \u2014 contract compatibility,\nreconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\nreport the service healthy when any required financial-control signal is\nunavailable.\n", + "requiresTools": [ + "helio/tools/repository-signals@1.0.0" + ], + "variants": { + "a2a": "Apply the repository-status capability for the payments service and return\nattributed evidence for the calling agent \u2014 contract compatibility,\nreconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\nreport the service healthy when any required financial-control signal is\nunavailable.\n", + "manual": "Apply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n", + "pipeline": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n" + } + } + } + ], + "x-typeference": { + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ] + } + }, + "metadata": { + "generatedBy": "TypeFerence", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a", + "sourceIdentifier": "urn:air:helio.example:typeference:source:helio" + }, + "trustManifest": { + "identity": "https://helio.example", + "identityType": "https", + "provenance": [ + { + "relation": "derivedFrom", + "sourceId": "urn:air:helio.example:typeference:source:helio", + "sourceDigest": "sha256:d729282512cfb50ecfc639c8e126b4897018abe2842a2800238ced8af39b148a" } ] } diff --git a/dist/ard/executive-assistant.agent-card.json b/dist/ard/executive-assistant.agent-card.json new file mode 100644 index 0000000..e98b1cd --- /dev/null +++ b/dist/ard/executive-assistant.agent-card.json @@ -0,0 +1,40 @@ +{ + "protocolVersion": "0.3.0", + "name": "Helio Executive Assistant", + "description": "Coordinates an executive\u0027s correspondence, briefings, and cross-agent requests.", + "version": "1.0.0", + "url": "https://helio.example/a2a/executive-assistant", + "preferredTransport": "JSONRPC", + "capabilities": { + "streaming": false, + "pushNotifications": false + }, + "defaultInputModes": [ + "application/json" + ], + "defaultOutputModes": [ + "application/json" + ], + "provider": { + "organization": "helio.example", + "url": "https://helio.example" + }, + "skills": [ + { + "id": "executive-assistant.prepare-brief", + "name": "prepare-brief", + "description": "Assemble an executive brief, requesting repository evidence when needed.", + "tags": [ + "typeference" + ] + }, + { + "id": "executive-assistant.triage-message", + "name": "triage-message", + "description": "Classify an inbound message and recommend an accountable next action.", + "tags": [ + "typeference" + ] + } + ] +} diff --git a/dist/ard/executive-assistant.mcp.json b/dist/ard/executive-assistant.mcp.json new file mode 100644 index 0000000..99ceb0a --- /dev/null +++ b/dist/ard/executive-assistant.mcp.json @@ -0,0 +1,100 @@ +{ + "tools": [ + { + "name": "executive-assistant.prepare-brief", + "description": "Assemble an executive brief, requesting repository evidence when needed.", + "inputSchema": { + "type": "object", + "properties": { + "topic": { + "type": "string" + }, + "evidence": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "topic" + ], + "additionalProperties": false + }, + "outputSchema": { + "type": "object", + "properties": { + "brief": { + "type": "string" + }, + "openQuestions": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "brief", + "openQuestions" + ] + }, + "x-typeference": { + "instructionsTemplate": "Build a decision-oriented brief from the supplied topic and evidence.\nWhen repository status is material, request \u0060payments-repo-agent.repository-status\u0060 and incorporate its returned evidence with attribution.\n", + "requiresTools": [] + } + }, + { + "name": "executive-assistant.triage-message", + "description": "Classify an inbound message and recommend an accountable next action.", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + }, + "outputSchema": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "urgency": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "nextAction": { + "type": "string" + } + }, + "required": [ + "summary", + "urgency", + "owner", + "nextAction" + ] + }, + "x-typeference": { + "instructionsTemplate": "Read the message and identify its sender, intent, urgency, decision owner, and requested deadline.\nSeparate facts from assumptions. Return a concise recommendation; do not send a reply.\n", + "requiresTools": [] + } + } + ], + "x-typeference": { + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ] + } +} diff --git a/dist/ard/payments-repo-agent.agent-card.json b/dist/ard/payments-repo-agent.agent-card.json new file mode 100644 index 0000000..a576418 --- /dev/null +++ b/dist/ard/payments-repo-agent.agent-card.json @@ -0,0 +1,32 @@ +{ + "protocolVersion": "0.3.0", + "name": "Helio Payments Repository Agent", + "description": "Specializes repository assistance for the fictional payments service.", + "version": "1.0.0", + "url": "https://helio.example/a2a/payments-repo-agent", + "preferredTransport": "JSONRPC", + "capabilities": { + "streaming": false, + "pushNotifications": false + }, + "defaultInputModes": [ + "application/json" + ], + "defaultOutputModes": [ + "application/json" + ], + "provider": { + "organization": "helio.example", + "url": "https://helio.example" + }, + "skills": [ + { + "id": "payments-repo-agent.repository-status", + "name": "repository-status", + "description": "Report payments-service health with contract and reconciliation evidence.", + "tags": [ + "typeference" + ] + } + ] +} diff --git a/dist/ard/payments-repo-agent.mcp.json b/dist/ard/payments-repo-agent.mcp.json new file mode 100644 index 0000000..7b1f29d --- /dev/null +++ b/dist/ard/payments-repo-agent.mcp.json @@ -0,0 +1,62 @@ +{ + "tools": [ + { + "name": "payments-repo-agent.repository-status", + "description": "Report payments-service health with contract and reconciliation evidence.", + "inputSchema": { + "type": "object", + "properties": { + "focus": { + "type": "string" + } + }, + "additionalProperties": false + }, + "outputSchema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "evidence": { + "type": "array", + "items": { + "type": "string" + } + }, + "risks": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "status", + "evidence", + "risks" + ] + }, + "x-typeference": { + "instructionsTemplate": "Apply the repository-status capability for the payments service and return\nattributed evidence for the calling agent \u2014 contract compatibility,\nreconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\nreport the service healthy when any required financial-control signal is\nunavailable.\n", + "requiresTools": [ + "helio/tools/repository-signals@1.0.0" + ], + "variants": { + "a2a": "Apply the repository-status capability for the payments service and return\nattributed evidence for the calling agent \u2014 contract compatibility,\nreconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\nreport the service healthy when any required financial-control signal is\nunavailable.\n", + "manual": "Apply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n", + "pipeline": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n" + } + } + } + ], + "x-typeference": { + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ] + } +} diff --git a/dist/codex/executive-assistant/.agents/skills/prepare-brief/SKILL.md b/dist/codex/executive-assistant/.agents/skills/prepare-brief/SKILL.md index 0b2f4b4..003151e 100644 --- a/dist/codex/executive-assistant/.agents/skills/prepare-brief/SKILL.md +++ b/dist/codex/executive-assistant/.agents/skills/prepare-brief/SKILL.md @@ -9,6 +9,5 @@ When repository status is material, request `payments-repo-agent.repository-stat ## Context loaded on invocation - `context/organization.md` -- `context/safety-policy.md` - `context/principal.md` - `context/executive-rhythm.md` diff --git a/dist/codex/executive-assistant/.agents/skills/triage-message/SKILL.md b/dist/codex/executive-assistant/.agents/skills/triage-message/SKILL.md index c349483..fbd6263 100644 --- a/dist/codex/executive-assistant/.agents/skills/triage-message/SKILL.md +++ b/dist/codex/executive-assistant/.agents/skills/triage-message/SKILL.md @@ -9,5 +9,4 @@ Separate facts from assumptions. Return a concise recommendation; do not send a ## Context loaded on invocation - `context/organization.md` -- `context/safety-policy.md` - `context/principal.md` diff --git a/dist/codex/executive-assistant/.typeference/bundle.json b/dist/codex/executive-assistant/.typeference/bundle.json index c343d10..7622abb 100644 --- a/dist/codex/executive-assistant/.typeference/bundle.json +++ b/dist/codex/executive-assistant/.typeference/bundle.json @@ -11,8 +11,7 @@ ], "slots": { "organization": "context/organization.md", - "principal": "context/principal.md", - "safetyPolicy": "context/safety-policy.md" + "principal": "context/principal.md" }, "workingNorms": [ "Preserve a clear audit trail for material decisions.", @@ -21,10 +20,16 @@ ], "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md", "context/executive-rhythm.md" ], + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ], "skills": [ { "dispatchName": "executive-assistant.prepare-brief", @@ -36,7 +41,6 @@ "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022brief\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022openQuestions\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022brief\u0022,\u0022openQuestions\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md", "context/executive-rhythm.md" ], @@ -61,7 +65,6 @@ "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022summary\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022urgency\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022owner\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022nextAction\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022summary\u0022,\u0022urgency\u0022,\u0022owner\u0022,\u0022nextAction\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md" ], "provenance": [ @@ -81,10 +84,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -97,10 +96,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/person-defaults@1.0.0" diff --git a/dist/codex/executive-assistant/AGENTS.md b/dist/codex/executive-assistant/AGENTS.md index 8af3450..8afbb62 100644 --- a/dist/codex/executive-assistant/AGENTS.md +++ b/dist/codex/executive-assistant/AGENTS.md @@ -12,7 +12,14 @@ Coordinates an executive's correspondence, briefings, and cross-agent requests. - `organization`: `context/organization.md` - `principal`: `context/principal.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills diff --git a/dist/codex/payments-repo-agent/.agents/skills/repository-status/SKILL.md b/dist/codex/payments-repo-agent/.agents/skills/repository-status/SKILL.md index 9e8fcf8..6314505 100644 --- a/dist/codex/payments-repo-agent/.agents/skills/repository-status/SKILL.md +++ b/dist/codex/payments-repo-agent/.agents/skills/repository-status/SKILL.md @@ -3,12 +3,13 @@ name: repository-status description: "Report payments-service health with contract and reconciliation evidence." --- -Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness. -Do not report the service healthy when any required financial-control signal is unavailable. +Apply the repository-status capability for the payments service, then include +payment-contract compatibility, reconciliation checks, and rollback readiness, +surfacing uncertainty conversationally. Do not report the service healthy when +any required financial-control signal is unavailable. ## Context loaded on invocation - `context/organization.md` -- `context/safety-policy.md` - `context/repository.md` - `context/payments-service.md` diff --git a/dist/codex/payments-repo-agent/.typeference/bundle.json b/dist/codex/payments-repo-agent/.typeference/bundle.json index 1396aa0..4495a5e 100644 --- a/dist/codex/payments-repo-agent/.typeference/bundle.json +++ b/dist/codex/payments-repo-agent/.typeference/bundle.json @@ -11,8 +11,7 @@ ], "slots": { "organization": "context/organization.md", - "repository": "context/repository.md", - "safetyPolicy": "context/safety-policy.md" + "repository": "context/repository.md" }, "workingNorms": [ "Preserve a clear audit trail for material decisions.", @@ -21,25 +20,35 @@ ], "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/repository.md", "context/payments-service.md" ], + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ], "skills": [ { "dispatchName": "payments-repo-agent.repository-status", "capabilityId": "helio/capabilities/repository-status@1.0.0", "implementationId": "helio/skills/payments-repository-status@1.0.0", "description": "Report payments-service health with contract and reconciliation evidence.", - "instructions": "Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\nDo not report the service healthy when any required financial-control signal is unavailable.\n", + "instructions": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n", "inputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}", "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/repository.md", "context/payments-service.md" ], + "variants": { + "a2a": "Apply the repository-status capability for the payments service and return\nattributed evidence for the calling agent \u2014 contract compatibility,\nreconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\nreport the service healthy when any required financial-control signal is\nunavailable.\n", + "manual": "Apply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n", + "pipeline": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n" + }, "provenance": [ { "field": "skill.capability", @@ -57,10 +66,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -73,10 +78,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/repository-defaults@1.0.0" diff --git a/dist/codex/payments-repo-agent/AGENTS.md b/dist/codex/payments-repo-agent/AGENTS.md index 915fa0a..41e6dad 100644 --- a/dist/codex/payments-repo-agent/AGENTS.md +++ b/dist/codex/payments-repo-agent/AGENTS.md @@ -12,7 +12,14 @@ Specializes repository assistance for the fictional payments service. - `organization`: `context/organization.md` - `repository`: `context/repository.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills diff --git a/dist/copilot/executive-assistant/.github/agents/executive-assistant.agent.md b/dist/copilot/executive-assistant/.github/agents/executive-assistant.agent.md index 6e587d3..6f6c840 100644 --- a/dist/copilot/executive-assistant/.github/agents/executive-assistant.agent.md +++ b/dist/copilot/executive-assistant/.github/agents/executive-assistant.agent.md @@ -17,10 +17,25 @@ Coordinates an executive's correspondence, briefings, and cross-agent requests. - `organization`: `context/organization.md` - `principal`: `context/principal.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills - `executive-assistant.prepare-brief`: Assemble an executive brief, requesting repository evidence when needed. + +Build a decision-oriented brief from the supplied topic and evidence. +When repository status is material, request `payments-repo-agent.repository-status` and incorporate its returned evidence with attribution. + - `executive-assistant.triage-message`: Classify an inbound message and recommend an accountable next action. +Read the message and identify its sender, intent, urgency, decision owner, and requested deadline. +Separate facts from assumptions. Return a concise recommendation; do not send a reply. + + diff --git a/dist/copilot/executive-assistant/.github/copilot-instructions.md b/dist/copilot/executive-assistant/.github/copilot-instructions.md index 8af3450..9d97a66 100644 --- a/dist/copilot/executive-assistant/.github/copilot-instructions.md +++ b/dist/copilot/executive-assistant/.github/copilot-instructions.md @@ -12,10 +12,25 @@ Coordinates an executive's correspondence, briefings, and cross-agent requests. - `organization`: `context/organization.md` - `principal`: `context/principal.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills - `executive-assistant.prepare-brief`: Assemble an executive brief, requesting repository evidence when needed. + +Build a decision-oriented brief from the supplied topic and evidence. +When repository status is material, request `payments-repo-agent.repository-status` and incorporate its returned evidence with attribution. + - `executive-assistant.triage-message`: Classify an inbound message and recommend an accountable next action. +Read the message and identify its sender, intent, urgency, decision owner, and requested deadline. +Separate facts from assumptions. Return a concise recommendation; do not send a reply. + + diff --git a/dist/copilot/executive-assistant/.typeference/bundle.json b/dist/copilot/executive-assistant/.typeference/bundle.json index c343d10..7622abb 100644 --- a/dist/copilot/executive-assistant/.typeference/bundle.json +++ b/dist/copilot/executive-assistant/.typeference/bundle.json @@ -11,8 +11,7 @@ ], "slots": { "organization": "context/organization.md", - "principal": "context/principal.md", - "safetyPolicy": "context/safety-policy.md" + "principal": "context/principal.md" }, "workingNorms": [ "Preserve a clear audit trail for material decisions.", @@ -21,10 +20,16 @@ ], "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md", "context/executive-rhythm.md" ], + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ], "skills": [ { "dispatchName": "executive-assistant.prepare-brief", @@ -36,7 +41,6 @@ "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022brief\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022openQuestions\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022brief\u0022,\u0022openQuestions\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md", "context/executive-rhythm.md" ], @@ -61,7 +65,6 @@ "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022summary\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022urgency\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022owner\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022nextAction\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022summary\u0022,\u0022urgency\u0022,\u0022owner\u0022,\u0022nextAction\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md" ], "provenance": [ @@ -81,10 +84,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -97,10 +96,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/person-defaults@1.0.0" diff --git a/dist/copilot/payments-repo-agent/.github/agents/payments-repo-agent.agent.md b/dist/copilot/payments-repo-agent/.github/agents/payments-repo-agent.agent.md index 8801a5c..ee9c35d 100644 --- a/dist/copilot/payments-repo-agent/.github/agents/payments-repo-agent.agent.md +++ b/dist/copilot/payments-repo-agent/.github/agents/payments-repo-agent.agent.md @@ -17,9 +17,22 @@ Specializes repository assistance for the fictional payments service. - `organization`: `context/organization.md` - `repository`: `context/repository.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills - `payments-repo-agent.repository-status`: Report payments-service health with contract and reconciliation evidence. +Apply the repository-status capability for the payments service, then include +payment-contract compatibility, reconciliation checks, and rollback readiness, +surfacing uncertainty conversationally. Do not report the service healthy when +any required financial-control signal is unavailable. + + diff --git a/dist/copilot/payments-repo-agent/.github/copilot-instructions.md b/dist/copilot/payments-repo-agent/.github/copilot-instructions.md index 915fa0a..fdf04f3 100644 --- a/dist/copilot/payments-repo-agent/.github/copilot-instructions.md +++ b/dist/copilot/payments-repo-agent/.github/copilot-instructions.md @@ -12,9 +12,22 @@ Specializes repository assistance for the fictional payments service. - `organization`: `context/organization.md` - `repository`: `context/repository.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills - `payments-repo-agent.repository-status`: Report payments-service health with contract and reconciliation evidence. +Apply the repository-status capability for the payments service, then include +payment-contract compatibility, reconciliation checks, and rollback readiness, +surfacing uncertainty conversationally. Do not report the service healthy when +any required financial-control signal is unavailable. + + diff --git a/dist/copilot/payments-repo-agent/.typeference/bundle.json b/dist/copilot/payments-repo-agent/.typeference/bundle.json index 1396aa0..4495a5e 100644 --- a/dist/copilot/payments-repo-agent/.typeference/bundle.json +++ b/dist/copilot/payments-repo-agent/.typeference/bundle.json @@ -11,8 +11,7 @@ ], "slots": { "organization": "context/organization.md", - "repository": "context/repository.md", - "safetyPolicy": "context/safety-policy.md" + "repository": "context/repository.md" }, "workingNorms": [ "Preserve a clear audit trail for material decisions.", @@ -21,25 +20,35 @@ ], "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/repository.md", "context/payments-service.md" ], + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ], "skills": [ { "dispatchName": "payments-repo-agent.repository-status", "capabilityId": "helio/capabilities/repository-status@1.0.0", "implementationId": "helio/skills/payments-repository-status@1.0.0", "description": "Report payments-service health with contract and reconciliation evidence.", - "instructions": "Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\nDo not report the service healthy when any required financial-control signal is unavailable.\n", + "instructions": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n", "inputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}", "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/repository.md", "context/payments-service.md" ], + "variants": { + "a2a": "Apply the repository-status capability for the payments service and return\nattributed evidence for the calling agent \u2014 contract compatibility,\nreconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\nreport the service healthy when any required financial-control signal is\nunavailable.\n", + "manual": "Apply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n", + "pipeline": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n" + }, "provenance": [ { "field": "skill.capability", @@ -57,10 +66,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -73,10 +78,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/repository-defaults@1.0.0" diff --git a/dist/cursor/executive-assistant/.cursor/rules/executive-assistant.mdc b/dist/cursor/executive-assistant/.cursor/rules/executive-assistant.mdc index 1789371..eddc95c 100644 --- a/dist/cursor/executive-assistant/.cursor/rules/executive-assistant.mdc +++ b/dist/cursor/executive-assistant/.cursor/rules/executive-assistant.mdc @@ -18,10 +18,25 @@ Coordinates an executive's correspondence, briefings, and cross-agent requests. - `organization`: `context/organization.md` - `principal`: `context/principal.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills - `executive-assistant.prepare-brief`: Assemble an executive brief, requesting repository evidence when needed. + +Build a decision-oriented brief from the supplied topic and evidence. +When repository status is material, request `payments-repo-agent.repository-status` and incorporate its returned evidence with attribution. + - `executive-assistant.triage-message`: Classify an inbound message and recommend an accountable next action. +Read the message and identify its sender, intent, urgency, decision owner, and requested deadline. +Separate facts from assumptions. Return a concise recommendation; do not send a reply. + + diff --git a/dist/cursor/executive-assistant/.typeference/bundle.json b/dist/cursor/executive-assistant/.typeference/bundle.json index c343d10..7622abb 100644 --- a/dist/cursor/executive-assistant/.typeference/bundle.json +++ b/dist/cursor/executive-assistant/.typeference/bundle.json @@ -11,8 +11,7 @@ ], "slots": { "organization": "context/organization.md", - "principal": "context/principal.md", - "safetyPolicy": "context/safety-policy.md" + "principal": "context/principal.md" }, "workingNorms": [ "Preserve a clear audit trail for material decisions.", @@ -21,10 +20,16 @@ ], "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md", "context/executive-rhythm.md" ], + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ], "skills": [ { "dispatchName": "executive-assistant.prepare-brief", @@ -36,7 +41,6 @@ "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022brief\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022openQuestions\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022brief\u0022,\u0022openQuestions\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md", "context/executive-rhythm.md" ], @@ -61,7 +65,6 @@ "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022summary\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022urgency\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022owner\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022nextAction\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022summary\u0022,\u0022urgency\u0022,\u0022owner\u0022,\u0022nextAction\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md" ], "provenance": [ @@ -81,10 +84,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -97,10 +96,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/person-defaults@1.0.0" diff --git a/dist/cursor/executive-assistant/AGENTS.md b/dist/cursor/executive-assistant/AGENTS.md index 8af3450..9d97a66 100644 --- a/dist/cursor/executive-assistant/AGENTS.md +++ b/dist/cursor/executive-assistant/AGENTS.md @@ -12,10 +12,25 @@ Coordinates an executive's correspondence, briefings, and cross-agent requests. - `organization`: `context/organization.md` - `principal`: `context/principal.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills - `executive-assistant.prepare-brief`: Assemble an executive brief, requesting repository evidence when needed. + +Build a decision-oriented brief from the supplied topic and evidence. +When repository status is material, request `payments-repo-agent.repository-status` and incorporate its returned evidence with attribution. + - `executive-assistant.triage-message`: Classify an inbound message and recommend an accountable next action. +Read the message and identify its sender, intent, urgency, decision owner, and requested deadline. +Separate facts from assumptions. Return a concise recommendation; do not send a reply. + + diff --git a/dist/cursor/payments-repo-agent/.cursor/rules/payments-repo-agent.mdc b/dist/cursor/payments-repo-agent/.cursor/rules/payments-repo-agent.mdc index 371ddf3..165b045 100644 --- a/dist/cursor/payments-repo-agent/.cursor/rules/payments-repo-agent.mdc +++ b/dist/cursor/payments-repo-agent/.cursor/rules/payments-repo-agent.mdc @@ -18,9 +18,22 @@ Specializes repository assistance for the fictional payments service. - `organization`: `context/organization.md` - `repository`: `context/repository.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills - `payments-repo-agent.repository-status`: Report payments-service health with contract and reconciliation evidence. +Apply the repository-status capability for the payments service, then include +payment-contract compatibility, reconciliation checks, and rollback readiness, +surfacing uncertainty conversationally. Do not report the service healthy when +any required financial-control signal is unavailable. + + diff --git a/dist/cursor/payments-repo-agent/.typeference/bundle.json b/dist/cursor/payments-repo-agent/.typeference/bundle.json index 1396aa0..4495a5e 100644 --- a/dist/cursor/payments-repo-agent/.typeference/bundle.json +++ b/dist/cursor/payments-repo-agent/.typeference/bundle.json @@ -11,8 +11,7 @@ ], "slots": { "organization": "context/organization.md", - "repository": "context/repository.md", - "safetyPolicy": "context/safety-policy.md" + "repository": "context/repository.md" }, "workingNorms": [ "Preserve a clear audit trail for material decisions.", @@ -21,25 +20,35 @@ ], "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/repository.md", "context/payments-service.md" ], + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ], "skills": [ { "dispatchName": "payments-repo-agent.repository-status", "capabilityId": "helio/capabilities/repository-status@1.0.0", "implementationId": "helio/skills/payments-repository-status@1.0.0", "description": "Report payments-service health with contract and reconciliation evidence.", - "instructions": "Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\nDo not report the service healthy when any required financial-control signal is unavailable.\n", + "instructions": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n", "inputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}", "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/repository.md", "context/payments-service.md" ], + "variants": { + "a2a": "Apply the repository-status capability for the payments service and return\nattributed evidence for the calling agent \u2014 contract compatibility,\nreconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\nreport the service healthy when any required financial-control signal is\nunavailable.\n", + "manual": "Apply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n", + "pipeline": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n" + }, "provenance": [ { "field": "skill.capability", @@ -57,10 +66,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -73,10 +78,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/repository-defaults@1.0.0" diff --git a/dist/cursor/payments-repo-agent/AGENTS.md b/dist/cursor/payments-repo-agent/AGENTS.md index 915fa0a..fdf04f3 100644 --- a/dist/cursor/payments-repo-agent/AGENTS.md +++ b/dist/cursor/payments-repo-agent/AGENTS.md @@ -12,9 +12,22 @@ Specializes repository assistance for the fictional payments service. - `organization`: `context/organization.md` - `repository`: `context/repository.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills - `payments-repo-agent.repository-status`: Report payments-service health with contract and reconciliation evidence. +Apply the repository-status capability for the payments service, then include +payment-contract compatibility, reconciliation checks, and rollback readiness, +surfacing uncertainty conversationally. Do not report the service healthy when +any required financial-control signal is unavailable. + + diff --git a/dist/neutral/executive-assistant/AGENTS.md b/dist/neutral/executive-assistant/AGENTS.md index 8af3450..8afbb62 100644 --- a/dist/neutral/executive-assistant/AGENTS.md +++ b/dist/neutral/executive-assistant/AGENTS.md @@ -12,7 +12,14 @@ Coordinates an executive's correspondence, briefings, and cross-agent requests. - `organization`: `context/organization.md` - `principal`: `context/principal.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills diff --git a/dist/neutral/executive-assistant/bundle.json b/dist/neutral/executive-assistant/bundle.json index c343d10..7622abb 100644 --- a/dist/neutral/executive-assistant/bundle.json +++ b/dist/neutral/executive-assistant/bundle.json @@ -11,8 +11,7 @@ ], "slots": { "organization": "context/organization.md", - "principal": "context/principal.md", - "safetyPolicy": "context/safety-policy.md" + "principal": "context/principal.md" }, "workingNorms": [ "Preserve a clear audit trail for material decisions.", @@ -21,10 +20,16 @@ ], "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md", "context/executive-rhythm.md" ], + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ], "skills": [ { "dispatchName": "executive-assistant.prepare-brief", @@ -36,7 +41,6 @@ "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022brief\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022openQuestions\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022brief\u0022,\u0022openQuestions\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md", "context/executive-rhythm.md" ], @@ -61,7 +65,6 @@ "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022summary\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022urgency\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022owner\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022nextAction\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022required\u0022:[\u0022summary\u0022,\u0022urgency\u0022,\u0022owner\u0022,\u0022nextAction\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/principal.md" ], "provenance": [ @@ -81,10 +84,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -97,10 +96,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/person-defaults@1.0.0" diff --git a/dist/neutral/executive-assistant/provenance.json b/dist/neutral/executive-assistant/provenance.json index 4bd2556..7544a30 100644 --- a/dist/neutral/executive-assistant/provenance.json +++ b/dist/neutral/executive-assistant/provenance.json @@ -3,10 +3,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -19,10 +15,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/person-defaults@1.0.0" diff --git a/dist/neutral/executive-assistant/skills/prepare-brief/SKILL.md b/dist/neutral/executive-assistant/skills/prepare-brief/SKILL.md index 0b2f4b4..003151e 100644 --- a/dist/neutral/executive-assistant/skills/prepare-brief/SKILL.md +++ b/dist/neutral/executive-assistant/skills/prepare-brief/SKILL.md @@ -9,6 +9,5 @@ When repository status is material, request `payments-repo-agent.repository-stat ## Context loaded on invocation - `context/organization.md` -- `context/safety-policy.md` - `context/principal.md` - `context/executive-rhythm.md` diff --git a/dist/neutral/executive-assistant/skills/triage-message/SKILL.md b/dist/neutral/executive-assistant/skills/triage-message/SKILL.md index c349483..fbd6263 100644 --- a/dist/neutral/executive-assistant/skills/triage-message/SKILL.md +++ b/dist/neutral/executive-assistant/skills/triage-message/SKILL.md @@ -9,5 +9,4 @@ Separate facts from assumptions. Return a concise recommendation; do not send a ## Context loaded on invocation - `context/organization.md` -- `context/safety-policy.md` - `context/principal.md` diff --git a/dist/neutral/payments-repo-agent/AGENTS.md b/dist/neutral/payments-repo-agent/AGENTS.md index 915fa0a..41e6dad 100644 --- a/dist/neutral/payments-repo-agent/AGENTS.md +++ b/dist/neutral/payments-repo-agent/AGENTS.md @@ -12,7 +12,14 @@ Specializes repository assistance for the fictional payments service. - `organization`: `context/organization.md` - `repository`: `context/repository.md` -- `safetyPolicy`: `context/safety-policy.md` + +## Context + +### Helio Safety Policy + +# Safety policy + +Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. ## Available skills diff --git a/dist/neutral/payments-repo-agent/bundle.json b/dist/neutral/payments-repo-agent/bundle.json index 1396aa0..4495a5e 100644 --- a/dist/neutral/payments-repo-agent/bundle.json +++ b/dist/neutral/payments-repo-agent/bundle.json @@ -11,8 +11,7 @@ ], "slots": { "organization": "context/organization.md", - "repository": "context/repository.md", - "safetyPolicy": "context/safety-policy.md" + "repository": "context/repository.md" }, "workingNorms": [ "Preserve a clear audit trail for material decisions.", @@ -21,25 +20,35 @@ ], "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/repository.md", "context/payments-service.md" ], + "context": [ + { + "id": "helio/context/safety-policy@1.0.0", + "contextType": "helio/context-types/safety-policy@1.0.0", + "content": "# Safety policy\n\nAgents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority.\n" + } + ], "skills": [ { "dispatchName": "payments-repo-agent.repository-status", "capabilityId": "helio/capabilities/repository-status@1.0.0", "implementationId": "helio/skills/payments-repository-status@1.0.0", "description": "Report payments-service health with contract and reconciliation evidence.", - "instructions": "Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness.\nDo not report the service healthy when any required financial-control signal is unavailable.\n", + "instructions": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n", "inputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022focus\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022additionalProperties\u0022:false}", "outputSchema": "{\u0022type\u0022:\u0022object\u0022,\u0022properties\u0022:{\u0022status\u0022:{\u0022type\u0022:\u0022string\u0022},\u0022evidence\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}},\u0022risks\u0022:{\u0022type\u0022:\u0022array\u0022,\u0022items\u0022:{\u0022type\u0022:\u0022string\u0022}}},\u0022required\u0022:[\u0022status\u0022,\u0022evidence\u0022,\u0022risks\u0022]}", "contextFiles": [ "context/organization.md", - "context/safety-policy.md", "context/repository.md", "context/payments-service.md" ], + "variants": { + "a2a": "Apply the repository-status capability for the payments service and return\nattributed evidence for the calling agent \u2014 contract compatibility,\nreconciliation, and rollback readiness \u2014 with no user-facing framing. Do not\nreport the service healthy when any required financial-control signal is\nunavailable.\n", + "manual": "Apply the repository-status capability for the payments service, then include\npayment-contract compatibility, reconciliation checks, and rollback readiness,\nsurfacing uncertainty conversationally. Do not report the service healthy when\nany required financial-control signal is unavailable.\n", + "pipeline": "Apply the repository-status capability for the payments service and emit only\nthe strict output object. Mark any unavailable financial-control signal as an\nexplicit null; do not report the service healthy when one is missing.\n" + }, "provenance": [ { "field": "skill.capability", @@ -57,10 +66,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -73,10 +78,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/repository-defaults@1.0.0" diff --git a/dist/neutral/payments-repo-agent/provenance.json b/dist/neutral/payments-repo-agent/provenance.json index 6a5b701..0913c88 100644 --- a/dist/neutral/payments-repo-agent/provenance.json +++ b/dist/neutral/payments-repo-agent/provenance.json @@ -3,10 +3,6 @@ "field": "slots.organization", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "slots.safetyPolicy", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "workingNorms", "source": "helio/profiles/enterprise-defaults@1.0.0" @@ -19,10 +15,6 @@ "field": "contextFiles", "source": "helio/profiles/enterprise-defaults@1.0.0" }, - { - "field": "contextFiles", - "source": "helio/profiles/enterprise-defaults@1.0.0" - }, { "field": "embeds.helio/profiles/enterprise-defaults@1.0.0", "source": "helio/profiles/repository-defaults@1.0.0" diff --git a/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.a2a.md b/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.a2a.md new file mode 100644 index 0000000..f32cae0 --- /dev/null +++ b/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.a2a.md @@ -0,0 +1,16 @@ +--- +name: repository-status +description: "Report payments-service health with contract and reconciliation evidence." +--- + +Apply the repository-status capability for the payments service and return +attributed evidence for the calling agent — contract compatibility, +reconciliation, and rollback readiness — with no user-facing framing. Do not +report the service healthy when any required financial-control signal is +unavailable. + +## Context loaded on invocation + +- `context/organization.md` +- `context/repository.md` +- `context/payments-service.md` diff --git a/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.manual.md b/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.manual.md new file mode 100644 index 0000000..6314505 --- /dev/null +++ b/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.manual.md @@ -0,0 +1,15 @@ +--- +name: repository-status +description: "Report payments-service health with contract and reconciliation evidence." +--- + +Apply the repository-status capability for the payments service, then include +payment-contract compatibility, reconciliation checks, and rollback readiness, +surfacing uncertainty conversationally. Do not report the service healthy when +any required financial-control signal is unavailable. + +## Context loaded on invocation + +- `context/organization.md` +- `context/repository.md` +- `context/payments-service.md` diff --git a/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.md b/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.md index 9e8fcf8..eaebfe9 100644 --- a/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.md +++ b/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.md @@ -3,12 +3,12 @@ name: repository-status description: "Report payments-service health with contract and reconciliation evidence." --- -Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness. -Do not report the service healthy when any required financial-control signal is unavailable. +Apply the repository-status capability for the payments service and emit only +the strict output object. Mark any unavailable financial-control signal as an +explicit null; do not report the service healthy when one is missing. ## Context loaded on invocation - `context/organization.md` -- `context/safety-policy.md` - `context/repository.md` - `context/payments-service.md` diff --git a/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.pipeline.md b/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.pipeline.md new file mode 100644 index 0000000..eaebfe9 --- /dev/null +++ b/dist/neutral/payments-repo-agent/skills/repository-status/SKILL.pipeline.md @@ -0,0 +1,14 @@ +--- +name: repository-status +description: "Report payments-service health with contract and reconciliation evidence." +--- + +Apply the repository-status capability for the payments service and emit only +the strict output object. Mark any unavailable financial-control signal as an +explicit null; do not report the service healthy when one is missing. + +## Context loaded on invocation + +- `context/organization.md` +- `context/repository.md` +- `context/payments-service.md` diff --git a/docs/decisions/0012-invocation-mode-skill-variants.md b/docs/decisions/0012-invocation-mode-skill-variants.md new file mode 100644 index 0000000..b9c6182 --- /dev/null +++ b/docs/decisions/0012-invocation-mode-skill-variants.md @@ -0,0 +1,103 @@ +# 0012 — Invocation-mode skill variants + +**Status:** Accepted (2026-07-23) + +## Context + +A capability is a fixed contract: a versioned method slot with a stable +`inputSchema`/`outputSchema`. A skill is a concrete implementation that binds one +capability. + +Real agents need the *same* capability delivered differently to different +consumers. The motivating case is a repository-status agent whose output is +consumed three ways: + +- **pipeline** — automated/CI invocation; strict machine output, no prose, never + asks a clarifying question, marks unavailable signals explicitly. +- **manual** — a human in an interactive host (Copilot, Cursor); explains + reasoning, surfaces risk conversationally, offers the next accountable action. +- **a2a** — invoked by another agent (the cross-agent request path Helio already + exercises when `prepare-brief` requests `payments-repo-agent.repository-status`); + returns attributed evidence without user-facing framing. + +These three share the capability's I/O contract exactly. Only the +instructions/persona/delivery differ. The question is where that variation lives. + +The decisive observation: **mode does not change the contract.** A concern that +never touches `inputSchema`/`outputSchema` has no business living on the capability +or on the universal binding. It belongs on the one skill whose *rendering* varies. + +## Decision + +1. **`variants` is an opt-in property of a skill.** A skill declares *either* + `instructions` (unimodal — the default, unchanged, the common case) *or* + `variants:` — a mapping of mode name to `{ instructions, contextFiles?, + requiresContextTypes?, tools? }`. Skills with no modes are untouched and pay no + ceremony. + + ```yaml + kind: skill + id: helio/skills/repository-status@1.0.0 + binds: helio/capabilities/repository-status@1.0.0 + inputSchema: '...' # shared across variants + outputSchema: '...' # shared across variants + variants: + pipeline: { instructions: "Emit strict JSON, no prose; mark missing signals null." } + manual: { instructions: "Explain reasoning, surface risks, offer next action." } + a2a: { instructions: "Return attributed evidence for a calling agent." } + ``` + +2. **The capability and the binding are unchanged.** No `mode` appears on the + capability or on `SkillBinding`. A variant may set `instructions`, + `contextFiles`/context references, `requiresContextTypes` (ADR-0013), and tool + bindings (ADR-0017) — it may **not** override `inputSchema`/`outputSchema`. + Forking the contract is the one thing mode must never do, and the loader rejects + it. + +3. **The resolver's slot/ambiguity model is untouched.** A multimodal skill still + fills its capability slot exactly once; the promoted-name ambiguity check needs + no special case. Only the **emitter** fans a multimodal skill into one artifact + per variant. This is why this shape is preferred over a per-binding `mode` + discriminator, which would have forced the ambiguity checker to special-case + "same capability, different mode". + +4. **Variants may narrow context and tool requirements — the governance hook.** A + variant may raise `requiresContextTypes` to a stricter refinement (ADR-0013) and + bind a differently-scoped tool (ADR-0017): the `a2a`/`pipeline` variants can + *require* a governed context type and a governed-scope tool while `manual` + accepts the base. This is where "personal reads the personal vault, a2a reads + the governed one" is expressed as a **type constraint per variant**, not a + hardcoded path. + +5. **Variant selection is target-driven with an override.** Each target adapter + declares the default variant for the invocation surface it represents + (Copilot/Cursor → `manual`, MCP-callable/card → `a2a`, neutral/CI → `pipeline`), + overridable by a build flag. A target consuming a unimodal skill is unchanged. + +6. **Mode names are an open, author-defined vocabulary** with a recommended core + set (`pipeline`, `manual`, `a2a`) that target adapters key their defaults on. + +## Consequences + +- Authoring stays DRY: one agent, one skill, N faces. The distinction lives in the + type system, not in duplicated filenames. +- The common case (a skill with no modes) is entirely unaffected. +- **Cross-constraint with the on-disk format:** when TypeFerence source is written + as frontmatter-plus-body (the deferred `.tfer` serialization, referenced in + ADR-0013), "body = the skill's instructions" works only for a *unimodal* skill. A + multimodal skill's per-variant instructions live in structured frontmatter; the + body is either unused or carries the default variant. The format decision must be + written aware of this. +- Work required: a `variants` field in the loader/model with the "no schema + override" guard; emitter fan-out and per-adapter default-variant declaration; a + spec section; a golden-file fixture exercising a multimodal skill across targets. + +## Alternatives considered + +- **Agent-per-mode.** Duplicates the entire agent to vary one skill — the sprawl + TypeFerence exists to remove. Rejected as off-thesis. +- **`mode` on every `SkillBinding`.** Pollutes the ~90% of bindings that have no + modes and forces the ambiguity checker to special-case same-capability + multi-binding. Rejected. +- **`mode` in `inputSchema`.** Relocates the polymorphism to runtime, which the + definition layer does not model. Rejected. diff --git a/docs/decisions/0013-user-defined-typed-context.md b/docs/decisions/0013-user-defined-typed-context.md new file mode 100644 index 0000000..f8819d8 --- /dev/null +++ b/docs/decisions/0013-user-defined-typed-context.md @@ -0,0 +1,105 @@ +# 0013 — User-defined typed context + +**Status:** Accepted (2026-07-23) + +## Context + +Context today is untyped. `contextFiles` is a list of bare relative paths, and the +loader only checks that each path exists and does not escape the source root +(`go/internal/resource/loader.go`). Nothing distinguishes a principal-preferences +note from a safety policy from a "cast of characters" roster — they are all just +markdown at a path. It is also the one place the type system reaches *outside* +itself: a raw filesystem pointer to an opaque blob, the assembly-language escape +hatch inside the higher-level language. + +Two things are missing: + +1. **Naming a *kind* of context** — "cast of characters," "data-classification," a + particular vault's note shape — without that kind being a hardcoded TypeFerence + feature. TypeFerence ships the type *system*, not the types. +2. **Typing the boundary between a component and the knowledge it carries**, so a + mismatch is a build-time error rather than a runtime surprise. + +The clarifying frame: context is the **fields** half of the object model +(capabilities/skills are the methods; ADR-0015/0017). An agent *holds* typed +context; a skill *requires* the context types it needs to operate. + +## Decision + +1. **New resource kind `contextType`** — a first-class, versioned, user-defined + type. `id`, `displayName`, `description`, and an optional `schema` (JSON Schema + over the note's frontmatter). With `schema`, conformance is checked + structurally; without it, the type is a name tag (still useful for the gating in + decisions 4–5). "Cast of characters" and a vault's note shape are thus *defined + in YAML by a user*, never a language feature. + +2. **Context types embed/refine structurally, like interfaces.** A + `governedCastOfCharacters` may embed `castOfCharacters` and add provenance/owner + frontmatter; a governed roster *is a* cast of characters (satisfies the base). + This reuses the interface satisfaction machinery and is what makes a **trust + boundary a type** rather than a path: a variant or capability that requires the + governed refinement structurally rejects a bare personal note (ADR-0012 §4). + +3. **Agents/profiles HOLD context; skills REQUIRE context types.** Holding typed + context is agent/profile state (`context:`, formerly `contextFiles`). A skill + declares the types it needs: + + ```yaml + # skill + requiresContextTypes: [acme/context-types/my-obsidian-shape@1.0.0] + ``` + + The resolver checks that the holding component supplies context satisfying the + skill's required types — the same provide/require/check shape capabilities + already have. This is a *wiring* constraint, not a contract change: two skills + requiring different context types remain substitutable at their capability. + +4. **Governance gating.** A profile/agent/capability may declare + `allowedContextTypes` (a whitelist; anything outside it — direct, promoted, or + slot-filled — is a build error) and/or `requiresContextTypes` (must be present + after composition). Allow-lists **intersect** through embeds (the most + restrictive ancestor wins; embedding narrows, never widens). + +5. **Reference context by id, not by path.** `contextFiles: [context/x.md]` is + deprecated in favor of `context: [acme/notes/x@1.0.0]`, resolved through the + resource map like `embeds`/`skills`. This closes the last raw-path reference: a + context object is a first-class resource (`kind: context`) with an `id`, a + `contextType`, typed fields, and prose. Typed slots accept a context type, so + "fill the `vault` slot" becomes "fill it with a note of type X". + +6. **Types come from the type system, not the substrate.** Frontmatter/field values + are interpreted **schema-directed** against the declaring `contextType` — so a + `country: no` field is the string `"no"`, never the boolean, and YAML's implicit + coercions never fire. Determinism holds: contextType ids sort into the canonical + key space, field values canonicalize before hashing, and provenance records + which component contributed each typed context. + +## Consequences + +- "Cast of characters," "data-classification," and a vault's shape become things a + user *defines*, not things TypeFerence *has*. +- Context joins the type system fully — no raw path references remain. +- A real governance primitive: a profile or capability can constrain or require the + *kinds* of context flowing into anything built on it, the context analogue of + structural capability typing. +- The zero-ceremony default is preserved: untyped context keeps working; every new + field is opt-in. +- **The on-disk serialization is deferred.** How a context object is written on + disk (frontmatter-plus-body, a distinct `.tfer` extension, schema-directed + parsing, verbatim body, and whether Obsidian authors it natively) is a separate, + currently-parked decision — the *type semantics* here do not depend on it. The + reference-by-id and schema-directed-typing rulings above are the load-bearing + parts; the file format ADR will follow when the extension question is settled. +- Compile-time typed context is *Door A* (baked into artifacts); a context *type* + may also describe data a tool reads at runtime (*Door B*), where TypeFerence types + the shape but does not ship the reader (ADR-0019). + +## Alternatives considered + +- **Hardcode a fixed context taxonomy.** The exact anti-pattern the project rejects. + Ship the type system, not the types. Rejected. +- **Reference-side typing only** (`{ path, type }`, no id). Keeps the raw path and + lets a note be silently retyped per reference. Rejected in favor of first-class + context resources referenced by id. +- **Type context purely nominally.** Kept as the zero-schema case, but the optional + `schema` is what makes `requiresContextTypes` more than a naming convention. diff --git a/docs/decisions/0014-go-only-implementation.md b/docs/decisions/0014-go-only-implementation.md new file mode 100644 index 0000000..9c6541c --- /dev/null +++ b/docs/decisions/0014-go-only-implementation.md @@ -0,0 +1,80 @@ +# 0014 — Go-only implementation; the spec is the open invitation + +**Status:** Accepted (2026-07-23) + +Supersedes the dual-implementation commitments in ADR-0003, ADR-0004, and +ADR-0005 (the canonicalization rulings and the fixture corpus survive; the +requirement that *two* implementations agree does not). + +## Context + +TypeFerence began as two implementations — a C# reference (`src/`) and an +independent Go implementation (`go/`) — held to byte-identity by a shared +conformance suite (ADR-0005). C# came first because it was the author's more +familiar language; the model being compiled (Go-like embedding, structural +interfaces, promoted-name ambiguity) is Go-shaped. The C# implementation also +carries the only `serve` (MCP runtime) surface. + +Two implementations proved the specification is *normative* rather than a +description of one program's behavior. But that credential serves a constituency — +*other people who implement TypeFerence* — that does not currently exist, and it is +paid for on **every feature**: every change is built twice and held byte-identical. +The layer TypeFerence targets is still up for debate, and velocity on the tool +(compiler, LSP, `.tfer` tooling, Obsidian integration) is what earns adoption. None +of that is served by a second implementation. + +The decisive distinction: **conformance is not determinism.** + +- *Determinism* — one compiler produces byte-identical output from identical input — + is what makes `diff` meaningful, the committed-digest demos real, and governance + possible. It is a property of one good compiler. +- *Conformance* — two independent implementations agree — is the part that serves + the non-existent implementer constituency. + +Dropping the second implementation loses only the latter. + +## Decision + +1. **Go is the sole implementation.** Retire the C# reference implementation + (`src/`, `tests/`, `TypeFerence.slnx`, `Directory.Build.props`) and the C# CI/ + conformance jobs. One codebase, full velocity. + +2. **The specification remains the source of truth.** `docs/specification.md` stays + normative *in principle* — "here is the abstraction; realize it differently if + you want, but this repository's Go compiler is the one living answer." This keeps + the standard door open (a future second implementation could reappear) without + staffing it now, and preserves the accurate framing that TypeFerence is a *spec + with a reference implementation*, not merely a program. + +3. **The conformance suite becomes a Go golden-file determinism suite.** The + `conformance/fixtures/` corpus and canonicalization rulings (ADR-0004) survive + unchanged; the suite is re-pointed to prove "the Go compiler still emits the + committed bytes" instead of "Go and C# agree." The digest guarantee — the + property that matters — is fully retained. + +4. **`serve` is not ported (ADR-0018).** It is off-thesis (deployment/runtime), and + its value is reframed as *emitting a static callable-resource card*, not running + a server. Its retirement with C# costs the core nothing. + +## Consequences + +- Full-velocity single codebase; every subsequent ADR in this batch is implemented + once, not twice. +- Determinism, digests, `diff`, and provenance are unaffected. +- The `.slnx`/.NET toolchain requirement disappears from build and CI. +- The one concrete loss is `serve` as a working runtime; superseded by the + callable-card emission (ADR-0018). +- The spec is downgraded from "normative, proven by two implementations" to + "normative in principle, realized by one." Recoverable if a second implementation + ever returns; deliberately reversible. + +## Alternatives considered + +- **Keep dual-impl.** Justified only if TypeFerence's ambition is a *standard others + implement*. For a *tool people install*, it is a recurring tax on the wrong axis. + Rejected in favor of the enablement-first posture. +- **Collapse to "a Go package," drop the spec.** Fastest, but bricks the standard + door and downgrades the spec to "whatever the Go code does." Rejected: keeping the + spec doc costs almost nothing and preserves optionality. +- **Port `serve` to Go.** The most expensive port for the least critical-path value, + and off-thesis regardless. Rejected (ADR-0018). diff --git a/docs/decisions/0015-exposure-and-visibility.md b/docs/decisions/0015-exposure-and-visibility.md new file mode 100644 index 0000000..bff193e --- /dev/null +++ b/docs/decisions/0015-exposure-and-visibility.md @@ -0,0 +1,61 @@ +# 0015 — Exposure and visibility + +**Status:** Accepted (2026-07-23) + +## Context + +"Authoring an MCP tool" has felt like a missing concept. It is not: **MCP is the +wire projection of a capability across the definition boundary** (ADR-0018), and you +never author an MCP — you author a capability and *decide whether it is a public +entry point*. + +Most capabilities are internal composition plumbing: they exist to compose behavior +within an agent and nothing outside ever names them. A few are entry points meant to +be called by a consumer the definition does not control — another agent, a host +tool-loop, an external service. Today there is no way to say which is which, and the +existing C# `serve` exposed *every* skill as a tool, leaking internal plumbing as +public API. + +This is precisely Go's exported/unexported distinction — the public-API boundary — +and it should be modelled the same way. + +## Decision + +1. **Visibility is a per-capability property: `internal` (default) or `exposed`.** + Only `exposed` capabilities become part of an agent's public callable surface and + are projected into callable-resource cards (ADR-0018). This is Go's export model: + the boundary between public API and package-private plumbing. + +2. **Exposure is declared at the definition and promoted through embedding**, the + way Go promotes exported members of an embedded struct. An embedded profile's + exposed capabilities are promoted onto the embedder's public surface; internal + ones are not. This rides the promotion/ambiguity machinery the resolver already + has — visibility is one added attribute on the promoted member. + +3. **Default is `internal` (private-by-default), matching Go's unexported default.** + Exposing mints a *stable public API* — a name and schema outside callers depend + on — which is expensive to walk back; accidentally exposing all internal plumbing + is a worse, less-reversible trap than accidentally leaving something callable + internally. Private-by-default fails safe here. (Contrast the mutability axis in + ADR-0016, which is *open*-by-default; the unifying rule is per-axis Go defaults: + **open on override, private on export.**) + +4. **Exposure is a commitment, surfaced by tooling.** Because exposing is minting + public API, `inspect`/the LSP show which capabilities are exposed, and `diff` + flags an exposure change as the API-surface change it is. + +## Consequences + +- The only new authoring surface is a one-bit visibility marker; the default (all + internal) means an agent exposes nothing until deliberately opened. +- Callable-resource cards (ADR-0018) carry exactly the exposed capabilities — no + more blanket dumps of internal skills. +- Exposure composes through embedding without new machinery. + +## Alternatives considered + +- **A central `exposes:` list on the agent.** Less Go-native than + visibility-at-definition-plus-promotion, and it fragments the public surface away + from where members are declared. Rejected in favor of the promotion model. +- **Expose-by-default.** Turns internal composition into public API by omission — the + expensive, irreversible failure. Rejected. diff --git a/docs/decisions/0016-sealing-mutability-presence.md b/docs/decisions/0016-sealing-mutability-presence.md new file mode 100644 index 0000000..6fdb1f7 --- /dev/null +++ b/docs/decisions/0016-sealing-mutability-presence.md @@ -0,0 +1,77 @@ +# 0016 — Sealing: mutability and presence + +**Status:** Accepted (2026-07-23) + +## Context + +A security (or platform) team authors a profile that others embed. Some of its +skills are meant to be extension points others may override; others are guarantees +that must survive composition unmodified — if you mix in the safety profile, you use +its safety control as-is. Go's model does not support this: embedding promotes, but +an embedder may *shadow* a promoted member by redeclaring it. There is no `final`. +That is exactly what a governance foundation cannot allow — any embedder could +silently shadow the safety control and its guarantee would evaporate. + +This is the first place the Go aesthetic genuinely breaks: Go trusts the programmer, +so it has no `final`; a governance-composition layer cannot, because the whole point +is that one team's control survives another team's mix-in intact. + +## Decision + +1. **`sealed` modifier (mutability axis: `open` default / `sealed`).** A sealed + capability/skill may not be overridden, rebound, or suppressed by an embedder; + attempting to is a compile error. Crucially, **sealed forecloses *modification*, + not *extension*.** It is a `final` *member* on an *open* container: the profile + stays embeddable, you may add adjacent capabilities freely, you simply cannot dig + under the guaranteed member. Sealed member ≠ closed container. + +2. **The three operations sealed governs:** *add adjacent behavior* (always + allowed — this is extensibility, intact); *override/rebind the sealed capability* + (compile error); *remove/suppress it* (compile error). + +3. **`required` modifier (presence axis: `optional` default / `required`).** A + required capability must be present after composition and cannot be dropped. + `sealed + required` is a mandatory control: must be carried *and* cannot be + modified. + +4. **Open-by-default, enablement-first.** Everything is `open` unless explicitly + `sealed`. The people who need sealing (governance authors) are exactly the ones + who will remember to seal; open-by-default keeps ordinary composition frictionless + and keeps the divergence from Go *itself* opt-in — the type system is 100% + Go-shaped until someone explicitly reaches for the one primitive Go lacks. + (Combined with ADR-0015: per-axis Go defaults — **open on override, private on + export**.) + +5. **No special root.** A profile is a profile; "root" is only graph position + (embeds nothing / is embedded), not a distinct kind. Sealing is a uniform + modifier available on any profile/skill, never a foundation-only feature. + +6. **Sealing later is a breaking change; make it observable, not default.** Going + `open → sealed` can break embedders who were overriding. This is managed by + *visibility of seal state* — `inspect`/the LSP show sealed vs open, and `diff` + flags the transition — not by a stricter default. + +## Consequences + +- Governance foundations become real: a control can be handed to embedders that they + may build on but not weaken, enforced at compile time. +- Enablement stays the default; security is *achievable and observable* without + taxing ordinary composition. +- Some compositions become **illegal, by design**: two profiles that seal the same + capability to *different* skills cannot be embedded together (a hard error that + surfaces a real governance conflict). A sealed diamond (the same sealed member + reached by two paths) is conceptually fine — one member, no conflict — but note + the resolver's pre-existing same-depth promotion check currently rejects *any* + equal-depth diamond (sealed or not) with an ambiguity error before sealing is + considered; lifting that limitation is separate work. +- Three orthogonal axes now exist: visibility (ADR-0015), mutability, presence — all + permissive by default, every lock opt-in. + +## Alternatives considered + +- **Sealed-by-default for security foundations (fail-closed).** Protects authors who + forget to seal — but those are exactly the authors who don't need protection, and + it taxes every ordinary profile and breaks the uniform "no special root" rule. + Rejected in favor of open-by-default plus seal-state observability. +- **No sealing (stay pure Go).** Leaves governance guarantees unenforceable under + composition. Rejected; this is the deliberate, minimal divergence. diff --git a/docs/decisions/0017-tools-as-extern.md b/docs/decisions/0017-tools-as-extern.md new file mode 100644 index 0000000..c60a2df --- /dev/null +++ b/docs/decisions/0017-tools-as-extern.md @@ -0,0 +1,83 @@ +# 0017 — Tools as extern declarations + +**Status:** Accepted (2026-07-23) + +## Context + +Skills reference runtime tools — a vault reader, an API client — and today those +tools "float": a skill hopes the tool is available at runtime, with no typed +relationship. The root cause is that a tool has only a runtime **body** (the code +that runs, off in deployment) and no compile-time **declaration**. It is the same +untyped-reference problem ADR-0013 fixed for context, one layer over. + +There is also a vocabulary question. Checked against the MCP tools specification: +MCP uses **"capabilities"** for feature negotiation (a handshake) and **"tool"** for +an executable callable, and it *fuses* contract and implementation into that one +word. TypeFerence deliberately keeps them separate — which resolves "are we blending +skills and tools?": no, they are siblings. + +## Decision + +1. **`tool` is a first-class kind: an extern / header.** You author the tool's + *signature* — its `id`, interface (`inputSchema`/`outputSchema`), and the + auth/scope *shape* it demands — in the source. It carries **no context**. The + *body* (the code that actually reads the vault or calls the API) is implemented + outside the compilation unit, at runtime, by whoever deploys — exactly like a C + header, a `.d.ts`, or a Go interface for an external service. This reconciles + "the user writes the tool" (the body) with "tools are authored natively" (the + declaration): different halves. + +2. **The trio, un-fused (the object model's method layer):** + - **capability** — the contract ("what"); the *signature* half of an MCP tool. + - **skill** — **model**-fulfillment of a capability (the LLM does it via + instructions/context); *no MCP equivalent*. + - **tool** — **code**-fulfillment of a capability (an extern; the *implementation* + half of an MCP tool). + + Skill and tool are **siblings** — two fulfillment modalities of one capability, + not a blend. Previously only the model sibling existed; `tool` completes the pair. + +3. **Skills declare typed tool dependencies (`requiresTools`), compile-checked.** A + skill (or variant) declaring `requiresTools: [acme/tools/vault-reader@1.0.0]` is + verified at build: the tool is declared and its interface shape-matches. Runtime + hope becomes a build error. TypeFerence still never runs the tool. + +4. **One tool, auth/scope varies per scoped skill extension.** A tool is declared + once; each extension (an ADR-0012 variant) binds it with a different **scope and + credential *reference***, never the secret. The `manual`/personal extension binds + personal scope; the `a2a`/governed extension binds governed scope — and a governed + extension can be *required* to bind a governed-scope tool, enforced structurally + (the same "trust boundary is a type" move as ADR-0013 refinement). This matches + the MCP model, which says the tool set "MAY vary by the authorization presented on + the request — credentials are per-request input, not connection state." + +5. **Boundary rule:** TypeFerence types the **extern** (declaration, dependency, + scope-binding); it does not own the **body** (implementation, secrets, execution). + Same line as "TypeFerence embeds no LLM provider." + +6. **MCP naming crosswalk (also referenced by ADR-0018):** our **capability →** an + MCP **tool** (signature); MCP **"capabilities"** is a transport-layer negotiation + handshake *below* the TypeFerence layer and is **not** our `capability`. We keep + `capability` internally (it does coherent Go-interface-method work, and `interface` + is already a separate kind) and publish this crosswalk rather than renaming. + +## Consequences + +- Tools stop floating: every skill→tool dependency is declared and shape-checked at + build; no runtime "hope it's there." +- The method layer of the object model is complete and symmetric: one contract + (capability), two implementations (skill = model, tool = code). +- Auth is expressed as typed scope/credential references per extension; secrets never + enter the definition, consistent with the no-credentials boundary. +- Runtime tools (Door B, ADR-0019) are typed here but implemented by the deployer. + +## Alternatives considered + +- **Keep tools implicit/hoped-for at runtime.** The floating status quo; no + compile-time enforcement. Rejected. +- **Model a tool as just a capability with an "external" flag.** Understates it: a + tool needs an auth/scope shape and is the *code-fulfillment* sibling of a skill, not + merely a marked capability. Rejected in favor of a first-class extern kind. +- **Rename `capability` to avoid the MCP collision.** Churns a core, internally + coherent word (and collides with the existing `interface` kind). Rejected in favor + of a documented crosswalk. diff --git a/docs/decisions/0018-callable-resource-card-and-publishing.md b/docs/decisions/0018-callable-resource-card-and-publishing.md new file mode 100644 index 0000000..562c8d7 --- /dev/null +++ b/docs/decisions/0018-callable-resource-card-and-publishing.md @@ -0,0 +1,78 @@ +# 0018 — Callable-resource card and publishing + +**Status:** Accepted (2026-07-23) + +## Context + +The C# `serve` verb stood up an MCP server from compiled agents, turning each skill +into a live tool whose calls returned a deterministic invocation package. ADR-0014 +retires it as off-thesis runtime. But its *value* is not the running process — it is +a **packaging** decision, and that value should be recovered as a deterministic +emission rather than a server. + +Meanwhile `--emit-ard` already publishes ARD catalog entries — a source-package +entry and a precompiled-bundle entry per concrete agent/target — but it *explicitly +punts* on callable resources: the README notes that callable MCP/A2A resources +"require their own deployed endpoint and native card." `serve` assembled exactly that +native card in memory and then threw it away by only serving it live. The two nearly +conflict; in fact they complete each other. + +The design boundary is firm: **no ARD registry lifecycle, federation, dependency, or +deployment metadata in core semantics.** So emission is core; anything that *pushes* +to a registry is the deployment edge. + +## Decision + +1. **`serve`'s output becomes a static callable-resource card — the third ARD + archetype.** `--emit-ard` now emits three entry kinds: **source-package** + (existing; the design-time package feed a profile is fetched from to embed), + **precompiled-bundle** (existing; drop-in native artifact), and + **callable-resource card** (new; the fully-assembled invocation contract — exposed + capabilities per ADR-0015, their schemas, the instruction-package template, and + `derivedFrom` provenance). The card is static and deterministic; *answering* calls + is an optional thin host, not core. + +2. **You never author an MCP; MCP is a projection.** A capability projected *outbound* + across the boundary becomes a callable card (MCP tool / A2A card); a capability + required *inbound* is fulfilled by an external tool (ADR-0017, Door B). Both flatten + into "an MCP tool" on the wire; the skill/tool distinction is TypeFerence-internal. + MCP crosswalk (from ADR-0017): our `capability` → MCP **tool**; MCP **"capabilities"** + is transport-layer negotiation, unrelated to our `capability`. + +3. **`--emit-ard` defaults on when a publisher identity is configured.** ARD emission + needs a publisher domain, so it cannot hard-default; instead it is on whenever a + publisher identity is resolvable (flag or project config, e.g. + `typeference.trust.yaml`) and cleanly skipped with a hint otherwise. "The default + build output is catalog-ready" holds for projects that have declared who they are, + without failing a first-run `build` for a tire-kicker. + +4. **Publishing is an edge verb, not core.** The vocabulary chain is `build` → a + *catalog entry* → **`publish`** → a *registry*. Because pushing to a registry is + registry lifecycle (disclaimed by core), `publish` is an optional edge subcommand + (or an external registry tool consuming the emitted entry). Core *emits* the + registerable artifact deterministically; the network write is the edge. "Compile to + ARD as it exists" and "register to ARD as it exists" both **preserve the boundary**: + TypeFerence is a conformant *client* of an external standard, never the author of + registry lifecycle. + +## Consequences + +- The callable interface `serve` produced is now a first-class, static, registerable + artifact — exactly what an ARD-based agent finder needs (a card, not a live endpoint + to crawl). +- Porting an MCP server to Go (expensive, runtime, off-thesis) is replaced by emitting + a card (cheap, deterministic, on-thesis). +- `serve` the verb effectively evaporates into `--emit-ard`; a live host demotes to an + optional `host ` convenience that may be written later or never. +- The determinism/core boundary is respected: emission is deterministic; publication + is an explicit edge. + +## Alternatives considered + +- **Keep `serve` as a runtime.** Off-thesis and the most expensive thing to port + (ADR-0014). Rejected in favor of emission. +- **Put `publish` in the deterministic core.** Would pull registry lifecycle into core + semantics, which the design boundary disclaims. Rejected; `publish` is an edge. +- **Emit an MCP-server config as an output format.** Conflates transport with the + packaging decision; the card is the right, transport-neutral layer (bindable to MCP + or A2A). Rejected. diff --git a/docs/decisions/0019-context-lifecycles-two-doors.md b/docs/decisions/0019-context-lifecycles-two-doors.md new file mode 100644 index 0000000..dff934c --- /dev/null +++ b/docs/decisions/0019-context-lifecycles-two-doors.md @@ -0,0 +1,59 @@ +# 0019 — Context lifecycles: the two doors + +**Status:** Accepted (2026-07-23) + +## Context + +A knowledge vault (Obsidian or otherwise) relates to a TypeFerence agent in two +distinct ways, and conflating them breaks either determinism or governance. The two +sit on opposite sides of TypeFerence's most fundamental line — definition vs. +deployment ("no deployment state, hosted runtime, or model credentials" in the +definition layer). + +## Decision + +1. **Door A — compile-time context (baked in).** A context object (ADR-0013, an + instance of a `contextType`) exists as source and is compiled *into* the agent's + artifacts. Its content is part of the digest: reviewable, diffable, governed. + TypeFerence fully owns Door A — it reads the object and materializes it. + +2. **Door B — runtime context (queried live).** The compiled agent declares a + capability whose fulfillment is an external **tool** (ADR-0017); at execution the + *host* reads the live, mutable, possibly-private vault through that tool. + TypeFerence types only the contract — the `contextType` *shape* the data must + satisfy, the tool's signature, and the scope binding — and the content is **never** + in the digest. TypeFerence *describes* the access; it does not *perform* it. The + vault-reading tool body is **user-written** (the same boundary as "embeds no LLM + provider"). + +3. **One vocabulary, two lifecycles.** The same `contextType` (e.g. + `castOfCharacters`) is the invariant *shape* whether baked (A) or queried (B). The + split is a *relationship* — "is this content in the definition, or queried by the + deployment?" — not necessarily a folder split; a single note's *shape* can be + authored at design-time (A) while its *content* is read at run-time (B). + +4. **What TypeFerence ships for Door B: the contract, never the connector.** No vault + reader, no Obsidian runtime, nothing to maintain. `obsidian-vault-lookup` and + friends are *user-written* tools conforming to a typed contract TypeFerence emits. + +## Consequences + +- Determinism is protected: runtime content is never baked into a deterministic + artifact, so it can't leak into or destabilize the digest. +- Governance is protected: definitional content stays committed, reviewable, and + diffable rather than living as live external state. +- Scope stays honest and small — TypeFerence types the *what* (shape, contract, + scope) and stays out of the *how* at runtime. +- This is the lifecycle counterpart to the product framing: the vault is the informal + substrate; formalizing a slice *into* a definition is Door A, and typing a running + agent's *access* to the substrate is Door B. + +## Alternatives considered + +- **Treat the runtime vault like compile-time context (bake it in).** Bakes live, + mutable, possibly-private data into a deterministic artifact — kills determinism, + leaks data, couples the definition to transient state. Rejected. +- **Treat compile-time context as a live resource.** Definitional content would live + as external state, losing reviewability and governance. Rejected. +- **Ship a built-in vault/Obsidian connector.** Puts TypeFerence in the runtime + business it deliberately stays out of. Rejected; Door B tools are user-written. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index d4ecfd5..ab1cd94 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -20,3 +20,11 @@ Format: `NNNN-short-title.md` with sections **Status**, **Context**, **Decision* | [0009](0009-behavioral-equivalence-harness.md) | BETH: the behavioral equivalence test harness | | [0010](0010-browser-playground.md) | Browser playground: the Go compiler as WebAssembly | | [0011](0011-playground-live-runs.md) | Playground equivalence console: no secrets in the browser | +| [0012](0012-invocation-mode-skill-variants.md) | Invocation-mode skill variants (Accepted) | +| [0013](0013-user-defined-typed-context.md) | User-defined typed context (Accepted) | +| [0014](0014-go-only-implementation.md) | Go-only implementation; the spec is the open invitation (Accepted) | +| [0015](0015-exposure-and-visibility.md) | Exposure and visibility (Accepted) | +| [0016](0016-sealing-mutability-presence.md) | Sealing: mutability and presence (Accepted) | +| [0017](0017-tools-as-extern.md) | Tools as extern declarations (Accepted) | +| [0018](0018-callable-resource-card-and-publishing.md) | Callable-resource card and publishing (Accepted) | +| [0019](0019-context-lifecycles-two-doors.md) | Context lifecycles: the two doors (Accepted) | diff --git a/editors/vscode/.gitignore b/editors/vscode/.gitignore new file mode 100644 index 0000000..d3e15b1 --- /dev/null +++ b/editors/vscode/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +out/ +*.vsix diff --git a/editors/vscode/README.md b/editors/vscode/README.md new file mode 100644 index 0000000..e3d508d --- /dev/null +++ b/editors/vscode/README.md @@ -0,0 +1,42 @@ +# TypeFerence for VS Code + +A thin VS Code client for the TypeFerence language server. It associates `.tfer` +files with the `tfer` language (YAML frontmatter highlighted as YAML, the body as +markdown) and launches `typeference-lsp` to provide: + +- shape and workspace-composition diagnostics, +- completion of resource kinds and field names, +- go-to-definition on resource ids, +- document symbols. + +## Prerequisites + +Build and install the language server so it is on `PATH`: + +```sh +make build-lsp # produces bin/typeference-lsp +# put bin/ on PATH, or set the setting below +``` + +If it is not on `PATH`, set `"typeference.serverPath"` to its absolute location +in your VS Code settings. + +## Develop / run + +```sh +cd editors/vscode +npm install +``` + +Then open this folder in VS Code and press F5 to launch an Extension +Development Host, or package a `.vsix`: + +```sh +npx @vscode/vsce package +``` + +## Status + +The extension source is complete and self-contained; the LSP server it drives +(`internal/lsp`) is covered by Go tests. The extension itself has not been run +end-to-end in CI — build it with `npm install` and load it to try it. diff --git a/editors/vscode/extension.js b/editors/vscode/extension.js new file mode 100644 index 0000000..0f16fcf --- /dev/null +++ b/editors/vscode/extension.js @@ -0,0 +1,32 @@ +// Minimal VS Code client for the TypeFerence language server. It launches +// `typeference-lsp` (which must be on PATH) and drives it over stdio for .tfer +// sources: diagnostics, completion, go-to-definition, and document symbols. +const { workspace, window } = require("vscode"); +const { LanguageClient, TransportKind } = require("vscode-languageclient/node"); + +let client; + +function activate(context) { + const command = workspace.getConfiguration("typeference").get("serverPath") || "typeference-lsp"; + const server = { command, transport: TransportKind.stdio }; + const serverOptions = { run: server, debug: server }; + const clientOptions = { + documentSelector: [{ scheme: "file", language: "tfer" }], + synchronize: { + fileEvents: workspace.createFileSystemWatcher("**/*.{tfer,yaml}"), + }, + }; + client = new LanguageClient("typeference", "TypeFerence", serverOptions, clientOptions); + client.start().catch((err) => { + window.showErrorMessage( + "TypeFerence: could not start typeference-lsp (" + command + "). Is it on PATH? " + err.message, + ); + }); + context.subscriptions.push({ dispose: () => client && client.stop() }); +} + +function deactivate() { + return client ? client.stop() : undefined; +} + +module.exports = { activate, deactivate }; diff --git a/editors/vscode/language-configuration.json b/editors/vscode/language-configuration.json new file mode 100644 index 0000000..f7f8418 --- /dev/null +++ b/editors/vscode/language-configuration.json @@ -0,0 +1,24 @@ +{ + "comments": { + "lineComment": "#" + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ] +} diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json new file mode 100644 index 0000000..c76541f --- /dev/null +++ b/editors/vscode/package-lock.json @@ -0,0 +1,107 @@ +{ + "name": "typeference", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "typeference", + "version": "0.0.1", + "license": "Apache-2.0", + "dependencies": { + "vscode-languageclient": "^9.0.1" + }, + "devDependencies": { + "@types/vscode": "^1.75.0" + }, + "engines": { + "vscode": "^1.75.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.125.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.125.0.tgz", + "integrity": "sha512-0icm/ZQAaism87P0ekHqi4/Ju9du+Tm0RUW+y7vqRsxY2cY0FNRX1nAnaW7nT6npPt2tfHiheZ55Zm9UhqonFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", + "license": "MIT", + "dependencies": { + "minimatch": "^5.1.0", + "semver": "^7.3.7", + "vscode-languageserver-protocol": "3.17.5" + }, + "engines": { + "vscode": "^1.82.0" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + } + } +} diff --git a/editors/vscode/package.json b/editors/vscode/package.json new file mode 100644 index 0000000..4d3871d --- /dev/null +++ b/editors/vscode/package.json @@ -0,0 +1,47 @@ +{ + "name": "typeference", + "displayName": "TypeFerence", + "description": "Language support for TypeFerence .tfer sources — diagnostics, completion, go-to-definition, and symbols via typeference-lsp.", + "version": "0.0.1", + "publisher": "buchk", + "license": "Apache-2.0", + "engines": { + "vscode": "^1.75.0" + }, + "categories": [ + "Programming Languages", + "Linters" + ], + "activationEvents": [ + "onLanguage:tfer" + ], + "main": "./extension.js", + "contributes": { + "languages": [ + { + "id": "tfer", + "aliases": [ + "TypeFerence", + "tfer" + ], + "extensions": [ + ".tfer" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "tfer", + "scopeName": "source.tfer", + "path": "./syntaxes/tfer.tmLanguage.json" + } + ] + }, + "dependencies": { + "vscode-languageclient": "^9.0.1" + }, + "devDependencies": { + "@types/vscode": "^1.75.0" + } +} diff --git a/editors/vscode/syntaxes/tfer.tmLanguage.json b/editors/vscode/syntaxes/tfer.tmLanguage.json new file mode 100644 index 0000000..d55fd7f --- /dev/null +++ b/editors/vscode/syntaxes/tfer.tmLanguage.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "TypeFerence", + "scopeName": "source.tfer", + "patterns": [ + { + "comment": "YAML frontmatter between the leading --- fences", + "begin": "\\A(---)\\s*$", + "end": "^(---)\\s*$", + "beginCaptures": { + "1": { "name": "punctuation.definition.tag.begin.tfer" } + }, + "endCaptures": { + "1": { "name": "punctuation.definition.tag.end.tfer" } + }, + "name": "meta.embedded.block.frontmatter.tfer", + "patterns": [ + { "include": "source.yaml" } + ] + }, + { + "comment": "The body is markdown (skill instructions or context content)", + "include": "text.html.markdown" + } + ] +} diff --git a/examples/helio/agents/executive-assistant.agent.yaml b/examples/helio/agents/executive-assistant.agent.tfer similarity index 97% rename from examples/helio/agents/executive-assistant.agent.yaml rename to examples/helio/agents/executive-assistant.agent.tfer index 5bada56..f1db36d 100644 --- a/examples/helio/agents/executive-assistant.agent.yaml +++ b/examples/helio/agents/executive-assistant.agent.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: agent id: helio/executive-assistant@1.0.0 @@ -9,3 +10,4 @@ contextFiles: - context/executive-rhythm.md skills: - ref: helio/skills/prepare-brief@1.0.0 +--- diff --git a/examples/helio/agents/payments-repo-agent.agent.yaml b/examples/helio/agents/payments-repo-agent.agent.tfer similarity index 98% rename from examples/helio/agents/payments-repo-agent.agent.yaml rename to examples/helio/agents/payments-repo-agent.agent.tfer index 40fde20..d599b23 100644 --- a/examples/helio/agents/payments-repo-agent.agent.yaml +++ b/examples/helio/agents/payments-repo-agent.agent.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: agent id: helio/payments-repo-agent@1.0.0 @@ -10,3 +11,4 @@ contextFiles: skills: - ref: helio/skills/payments-repository-status@1.0.0 capability: helio/capabilities/repository-status@1.0.0 +--- diff --git a/examples/helio/capabilities/prepare-brief.capability.yaml b/examples/helio/capabilities/prepare-brief.capability.tfer similarity index 95% rename from examples/helio/capabilities/prepare-brief.capability.yaml rename to examples/helio/capabilities/prepare-brief.capability.tfer index 3eca798..1a8b436 100644 --- a/examples/helio/capabilities/prepare-brief.capability.yaml +++ b/examples/helio/capabilities/prepare-brief.capability.tfer @@ -1,7 +1,10 @@ +--- schemaVersion: 3 kind: capability id: helio/capabilities/prepare-brief@1.0.0 displayName: Prepare Brief description: Capability slot for assembling decision-oriented executive briefs. +visibility: exposed inputSchema: '{"type":"object","properties":{"topic":{"type":"string"},"evidence":{"type":"array","items":{"type":"string"}}},"required":["topic"],"additionalProperties":false}' outputSchema: '{"type":"object","properties":{"brief":{"type":"string"},"openQuestions":{"type":"array","items":{"type":"string"}}},"required":["brief","openQuestions"]}' +--- diff --git a/examples/helio/capabilities/repository-status.capability.yaml b/examples/helio/capabilities/repository-status.capability.tfer similarity index 94% rename from examples/helio/capabilities/repository-status.capability.yaml rename to examples/helio/capabilities/repository-status.capability.tfer index 27911ad..91f7c22 100644 --- a/examples/helio/capabilities/repository-status.capability.yaml +++ b/examples/helio/capabilities/repository-status.capability.tfer @@ -1,7 +1,10 @@ +--- schemaVersion: 3 kind: capability id: helio/capabilities/repository-status@1.0.0 displayName: Repository Status description: Capability slot for evidence-backed repository health summaries. +visibility: exposed inputSchema: '{"type":"object","properties":{"focus":{"type":"string"}},"additionalProperties":false}' outputSchema: '{"type":"object","properties":{"status":{"type":"string"},"evidence":{"type":"array","items":{"type":"string"}},"risks":{"type":"array","items":{"type":"string"}}},"required":["status","evidence","risks"]}' +--- diff --git a/examples/helio/capabilities/triage-message.capability.yaml b/examples/helio/capabilities/triage-message.capability.tfer similarity index 95% rename from examples/helio/capabilities/triage-message.capability.yaml rename to examples/helio/capabilities/triage-message.capability.tfer index 9a2121e..825d68d 100644 --- a/examples/helio/capabilities/triage-message.capability.yaml +++ b/examples/helio/capabilities/triage-message.capability.tfer @@ -1,7 +1,10 @@ +--- schemaVersion: 3 kind: capability id: helio/capabilities/triage-message@1.0.0 displayName: Triage Message description: Capability slot for classifying inbound messages and selecting accountable next actions. +visibility: exposed inputSchema: '{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}' outputSchema: '{"type":"object","properties":{"summary":{"type":"string"},"urgency":{"type":"string"},"owner":{"type":"string"},"nextAction":{"type":"string"}},"required":["summary","urgency","owner","nextAction"]}' +--- diff --git a/examples/helio/context-types/safety-policy.contexttype.tfer b/examples/helio/context-types/safety-policy.contexttype.tfer new file mode 100644 index 0000000..20ca19c --- /dev/null +++ b/examples/helio/context-types/safety-policy.contexttype.tfer @@ -0,0 +1,8 @@ +--- +schemaVersion: 3 +kind: contextType +id: helio/context-types/safety-policy@1.0.0 +displayName: Safety Policy +description: An organizational safety policy governing what an agent may do without explicit authority. +schema: '{"type":"object","required":["authority"],"properties":{"authority":{"type":"string"}}}' +--- diff --git a/examples/helio/context/safety-policy.md b/examples/helio/context/safety-policy.context.tfer similarity index 50% rename from examples/helio/context/safety-policy.md rename to examples/helio/context/safety-policy.context.tfer index cd25063..e2755b8 100644 --- a/examples/helio/context/safety-policy.md +++ b/examples/helio/context/safety-policy.context.tfer @@ -1,3 +1,11 @@ +--- +schemaVersion: 3 +kind: context +id: helio/context/safety-policy@1.0.0 +contextType: helio/context-types/safety-policy@1.0.0 +displayName: Helio Safety Policy +authority: explicit +--- # Safety policy Agents may prepare recommendations and drafts. They must not represent approval, transmit external messages, or make irreversible changes without explicit authority. diff --git a/examples/helio/interfaces/correspondent.interface.yaml b/examples/helio/interfaces/correspondent.interface.tfer similarity index 97% rename from examples/helio/interfaces/correspondent.interface.yaml rename to examples/helio/interfaces/correspondent.interface.tfer index 7339374..abcb903 100644 --- a/examples/helio/interfaces/correspondent.interface.yaml +++ b/examples/helio/interfaces/correspondent.interface.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: interface id: helio/interfaces/correspondent@1.0.0 @@ -7,3 +8,4 @@ requiresSlots: - principal requiresCapabilities: - helio/capabilities/triage-message@1.0.0 +--- diff --git a/examples/helio/interfaces/repository-observer.interface.yaml b/examples/helio/interfaces/repository-observer.interface.tfer similarity index 97% rename from examples/helio/interfaces/repository-observer.interface.yaml rename to examples/helio/interfaces/repository-observer.interface.tfer index 375e5bf..424a0e6 100644 --- a/examples/helio/interfaces/repository-observer.interface.yaml +++ b/examples/helio/interfaces/repository-observer.interface.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: interface id: helio/interfaces/repository-observer@1.0.0 @@ -7,3 +8,4 @@ requiresSlots: - repository requiresCapabilities: - helio/capabilities/repository-status@1.0.0 +--- diff --git a/examples/helio/profiles/enterprise-defaults.profile.yaml b/examples/helio/profiles/enterprise-defaults.profile.tfer similarity index 86% rename from examples/helio/profiles/enterprise-defaults.profile.yaml rename to examples/helio/profiles/enterprise-defaults.profile.tfer index 8306dc9..8a10412 100644 --- a/examples/helio/profiles/enterprise-defaults.profile.yaml +++ b/examples/helio/profiles/enterprise-defaults.profile.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: profile id: helio/profiles/enterprise-defaults@1.0.0 @@ -5,10 +6,11 @@ displayName: Helio Enterprise Defaults description: Shared organizational governance for every Helio resource. slots: organization: context/organization.md - safetyPolicy: context/safety-policy.md workingNorms: - Preserve a clear audit trail for material decisions. - State uncertainty and route work to an accountable owner when authority is unclear. contextFiles: - context/organization.md - - context/safety-policy.md +context: + - helio/context/safety-policy@1.0.0 +--- diff --git a/examples/helio/profiles/person-defaults.profile.yaml b/examples/helio/profiles/person-defaults.profile.tfer similarity index 94% rename from examples/helio/profiles/person-defaults.profile.yaml rename to examples/helio/profiles/person-defaults.profile.tfer index 73977ac..ec68fc8 100644 --- a/examples/helio/profiles/person-defaults.profile.yaml +++ b/examples/helio/profiles/person-defaults.profile.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: profile id: helio/profiles/person-defaults@1.0.0 @@ -13,3 +14,5 @@ contextFiles: - context/principal.md skills: - ref: helio/skills/triage-message@1.0.0 + sealed: true +--- diff --git a/examples/helio/profiles/repository-defaults.profile.yaml b/examples/helio/profiles/repository-defaults.profile.tfer similarity index 98% rename from examples/helio/profiles/repository-defaults.profile.yaml rename to examples/helio/profiles/repository-defaults.profile.tfer index 7fa7784..3b5d471 100644 --- a/examples/helio/profiles/repository-defaults.profile.yaml +++ b/examples/helio/profiles/repository-defaults.profile.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: profile id: helio/profiles/repository-defaults@1.0.0 @@ -13,3 +14,4 @@ contextFiles: - context/repository.md skills: - ref: helio/skills/repository-status@1.0.0 +--- diff --git a/examples/helio/skills/payments-repository-status.skill.tfer b/examples/helio/skills/payments-repository-status.skill.tfer new file mode 100644 index 0000000..bbf2430 --- /dev/null +++ b/examples/helio/skills/payments-repository-status.skill.tfer @@ -0,0 +1,33 @@ +--- +schemaVersion: 3 +kind: skill +id: helio/skills/payments-repository-status@1.0.0 +binds: helio/capabilities/repository-status@1.0.0 +displayName: Payments Repository Status +description: Report payments-service health with contract and reconciliation evidence. +contextFiles: + - context/payments-service.md +requiresTools: + - helio/tools/repository-signals@1.0.0 +inputSchema: '{"type":"object","properties":{"focus":{"type":"string"}},"additionalProperties":false}' +outputSchema: '{"type":"object","properties":{"status":{"type":"string"},"evidence":{"type":"array","items":{"type":"string"}},"risks":{"type":"array","items":{"type":"string"}}},"required":["status","evidence","risks"]}' +variants: + pipeline: + instructions: | + Apply the repository-status capability for the payments service and emit only + the strict output object. Mark any unavailable financial-control signal as an + explicit null; do not report the service healthy when one is missing. + manual: + instructions: | + Apply the repository-status capability for the payments service, then include + payment-contract compatibility, reconciliation checks, and rollback readiness, + surfacing uncertainty conversationally. Do not report the service healthy when + any required financial-control signal is unavailable. + a2a: + instructions: | + Apply the repository-status capability for the payments service and return + attributed evidence for the calling agent — contract compatibility, + reconciliation, and rollback readiness — with no user-facing framing. Do not + report the service healthy when any required financial-control signal is + unavailable. +--- diff --git a/examples/helio/skills/payments-repository-status.skill.yaml b/examples/helio/skills/payments-repository-status.skill.yaml deleted file mode 100644 index a46bb33..0000000 --- a/examples/helio/skills/payments-repository-status.skill.yaml +++ /dev/null @@ -1,13 +0,0 @@ -schemaVersion: 3 -kind: skill -id: helio/skills/payments-repository-status@1.0.0 -binds: helio/capabilities/repository-status@1.0.0 -displayName: Payments Repository Status -description: Report payments-service health with contract and reconciliation evidence. -contextFiles: - - context/payments-service.md -instructions: | - Apply the repository-status capability, then include payment-contract compatibility, reconciliation checks, and rollback readiness. - Do not report the service healthy when any required financial-control signal is unavailable. -inputSchema: '{"type":"object","properties":{"focus":{"type":"string"}},"additionalProperties":false}' -outputSchema: '{"type":"object","properties":{"status":{"type":"string"},"evidence":{"type":"array","items":{"type":"string"}},"risks":{"type":"array","items":{"type":"string"}}},"required":["status","evidence","risks"]}' diff --git a/examples/helio/skills/prepare-brief.skill.yaml b/examples/helio/skills/prepare-brief.skill.tfer similarity index 72% rename from examples/helio/skills/prepare-brief.skill.yaml rename to examples/helio/skills/prepare-brief.skill.tfer index ba4c2d6..702ec1e 100644 --- a/examples/helio/skills/prepare-brief.skill.yaml +++ b/examples/helio/skills/prepare-brief.skill.tfer @@ -1,3 +1,4 @@ +--- schemaVersion: 3 kind: skill id: helio/skills/prepare-brief@1.0.0 @@ -6,8 +7,8 @@ displayName: Prepare Brief description: Assemble an executive brief, requesting repository evidence when needed. contextFiles: - context/executive-rhythm.md -instructions: | - Build a decision-oriented brief from the supplied topic and evidence. - When repository status is material, request `payments-repo-agent.repository-status` and incorporate its returned evidence with attribution. inputSchema: '{"type":"object","properties":{"topic":{"type":"string"},"evidence":{"type":"array","items":{"type":"string"}}},"required":["topic"],"additionalProperties":false}' outputSchema: '{"type":"object","properties":{"brief":{"type":"string"},"openQuestions":{"type":"array","items":{"type":"string"}}},"required":["brief","openQuestions"]}' +--- +Build a decision-oriented brief from the supplied topic and evidence. +When repository status is material, request `payments-repo-agent.repository-status` and incorporate its returned evidence with attribution. diff --git a/examples/helio/skills/repository-status.skill.yaml b/examples/helio/skills/repository-status.skill.tfer similarity index 70% rename from examples/helio/skills/repository-status.skill.yaml rename to examples/helio/skills/repository-status.skill.tfer index 0502cda..eb91dd5 100644 --- a/examples/helio/skills/repository-status.skill.yaml +++ b/examples/helio/skills/repository-status.skill.tfer @@ -1,11 +1,12 @@ +--- schemaVersion: 3 kind: skill id: helio/skills/repository-status@1.0.0 binds: helio/capabilities/repository-status@1.0.0 displayName: Repository Status description: Summarize repository health using current version-control and test evidence. -instructions: | - Inspect the requested repository signals. Report branch state, recent material changes, test evidence, risks, and the next accountable action. - Mark any unavailable signal explicitly instead of inferring success. inputSchema: '{"type":"object","properties":{"focus":{"type":"string"}},"additionalProperties":false}' outputSchema: '{"type":"object","properties":{"status":{"type":"string"},"evidence":{"type":"array","items":{"type":"string"}},"risks":{"type":"array","items":{"type":"string"}}},"required":["status","evidence","risks"]}' +--- +Inspect the requested repository signals. Report branch state, recent material changes, test evidence, risks, and the next accountable action. +Mark any unavailable signal explicitly instead of inferring success. diff --git a/examples/helio/skills/triage-message.skill.yaml b/examples/helio/skills/triage-message.skill.tfer similarity index 68% rename from examples/helio/skills/triage-message.skill.yaml rename to examples/helio/skills/triage-message.skill.tfer index 8d0e69f..a5f397d 100644 --- a/examples/helio/skills/triage-message.skill.yaml +++ b/examples/helio/skills/triage-message.skill.tfer @@ -1,11 +1,14 @@ +--- schemaVersion: 3 kind: skill id: helio/skills/triage-message@1.0.0 binds: helio/capabilities/triage-message@1.0.0 displayName: Triage Message description: Classify an inbound message and recommend an accountable next action. -instructions: | - Read the message and identify its sender, intent, urgency, decision owner, and requested deadline. - Separate facts from assumptions. Return a concise recommendation; do not send a reply. +requiresContextTypes: + - helio/context-types/safety-policy@1.0.0 inputSchema: '{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}' outputSchema: '{"type":"object","properties":{"summary":{"type":"string"},"urgency":{"type":"string"},"owner":{"type":"string"},"nextAction":{"type":"string"}},"required":["summary","urgency","owner","nextAction"]}' +--- +Read the message and identify its sender, intent, urgency, decision owner, and requested deadline. +Separate facts from assumptions. Return a concise recommendation; do not send a reply. diff --git a/examples/helio/tools/repository-signals.tool.tfer b/examples/helio/tools/repository-signals.tool.tfer new file mode 100644 index 0000000..68a80bf --- /dev/null +++ b/examples/helio/tools/repository-signals.tool.tfer @@ -0,0 +1,11 @@ +--- +schemaVersion: 3 +kind: tool +id: helio/tools/repository-signals@1.0.0 +displayName: Repository Signals +description: >- + Reads version-control and test signals for a repository at runtime. TypeFerence + declares this extern; the deployer implements the body (ADR-0017, Door B). +inputSchema: '{"type":"object","properties":{"repository":{"type":"string"}},"required":["repository"],"additionalProperties":false}' +outputSchema: '{"type":"object","properties":{"branch":{"type":"string"},"tests":{"type":"string"}}}' +--- diff --git a/examples/helio/typeference.yaml b/examples/helio/typeference.yaml new file mode 100644 index 0000000..d3c132b --- /dev/null +++ b/examples/helio/typeference.yaml @@ -0,0 +1,4 @@ +schemaVersion: 1 +name: helio +version: 1.0.0 +publisher: helio.example diff --git a/examples/repo-agent/README.md b/examples/repo-agent/README.md new file mode 100644 index 0000000..e03ca04 --- /dev/null +++ b/examples/repo-agent/README.md @@ -0,0 +1,42 @@ +# repo-agent example + +A small corpus that exercises the object-model features from ADRs 0012–0019 in +one place. Build it with the Go implementation: + +```sh +typeference validate examples/repo-agent +typeference build examples/repo-agent --target neutral --out out +typeference inspect acme/repo-agent@1.0.0 --source examples/repo-agent +``` + +What each piece demonstrates: + +- **Exposure / visibility (ADR-0015).** `capabilities/repository-status` is + `visibility: exposed`, so the resolved skill is part of the agent's public + callable surface (`ResolvedAgent.ExposedSkills()`), from which a callable card + would be emitted. +- **Mode variants (ADR-0012).** `skills/repository-status` declares `variants` + for `pipeline`, `manual`, and `a2a` instead of a single `instructions`. The + neutral bundle emits a `variants` member (absent for unimodal skills); the + default rendering prefers `pipeline`. +- **Tools as extern (ADR-0017).** `tools/vault-reader` is a `tool` declaration + (an extern header). The skill's `requiresTools` names it, checked at build. + The tool's *body* is implemented by the deployer, not TypeFerence. +- **Typed context + refinement (ADR-0013).** `context-types/governed-cast` + embeds `cast-of-characters` (refinement). The skill `requiresContextTypes: + [cast-of-characters]`; the agent holds `notes/team` — a `governed-cast` — and + a governed cast *is a* cast, so the requirement is satisfied structurally. +- **Schema-directed fields (ADR-0013).** `governed-cast` declares a `schema` + requiring an `owner` field; `notes/team` carries `owner: Dana` in its + frontmatter. Remove it and the build fails with a missing-required-field + error — required fields accumulate across the refinement closure. +- **Sealing (ADR-0016).** `profiles/repo-defaults` binds the status skill + `sealed: true`. An agent embedding the profile may extend around it but cannot + override or rebind the sealed capability. +- **`.tfer` format (ADR-0013).** `notes/team.context.tfer` is authored as + frontmatter (typed fields) plus a markdown body (the roster content). + +Change the corpus to see the type system push back: drop `context:` from the +agent and the `requiresContextTypes` check fails; add a second binding of +`repository-status` on the agent and sealing rejects the override; delete the +`vault-reader` tool and `requiresTools` fails. diff --git a/examples/repo-agent/agents/repo-agent.agent.tfer b/examples/repo-agent/agents/repo-agent.agent.tfer new file mode 100644 index 0000000..bc79b28 --- /dev/null +++ b/examples/repo-agent/agents/repo-agent.agent.tfer @@ -0,0 +1,11 @@ +--- +schemaVersion: 3 +kind: agent +id: acme/repo-agent@1.0.0 +displayName: Acme Repo Agent +description: Reports repository status across pipeline, manual, and a2a surfaces. +embeds: + - acme/profiles/repo-defaults@1.0.0 +context: + - acme/notes/team@1.0.0 +--- diff --git a/examples/repo-agent/capabilities/repository-status.capability.tfer b/examples/repo-agent/capabilities/repository-status.capability.tfer new file mode 100644 index 0000000..5fe378b --- /dev/null +++ b/examples/repo-agent/capabilities/repository-status.capability.tfer @@ -0,0 +1,8 @@ +--- +schemaVersion: 3 +kind: capability +id: acme/capabilities/repository-status@1.0.0 +displayName: Repository Status +description: Evidence-backed repository health summary. +visibility: exposed +--- diff --git a/examples/repo-agent/context-types/cast-of-characters.contexttype.tfer b/examples/repo-agent/context-types/cast-of-characters.contexttype.tfer new file mode 100644 index 0000000..020ff54 --- /dev/null +++ b/examples/repo-agent/context-types/cast-of-characters.contexttype.tfer @@ -0,0 +1,7 @@ +--- +schemaVersion: 3 +kind: contextType +id: acme/context-types/cast-of-characters@1.0.0 +displayName: Cast of Characters +description: The people an agent must know — names, roles, relationships. +--- diff --git a/examples/repo-agent/context-types/governed-cast.contexttype.tfer b/examples/repo-agent/context-types/governed-cast.contexttype.tfer new file mode 100644 index 0000000..7898f5d --- /dev/null +++ b/examples/repo-agent/context-types/governed-cast.contexttype.tfer @@ -0,0 +1,10 @@ +--- +schemaVersion: 3 +kind: contextType +id: acme/context-types/governed-cast@1.0.0 +displayName: Governed Cast of Characters +description: A cast of characters refined with provenance and ownership metadata. +schema: '{"type":"object","required":["owner"],"properties":{"owner":{"type":"string"}}}' +embeds: + - acme/context-types/cast-of-characters@1.0.0 +--- diff --git a/examples/repo-agent/notes/team.context.tfer b/examples/repo-agent/notes/team.context.tfer new file mode 100644 index 0000000..5c0071c --- /dev/null +++ b/examples/repo-agent/notes/team.context.tfer @@ -0,0 +1,13 @@ +--- +schemaVersion: 3 +kind: context +id: acme/notes/team@1.0.0 +contextType: acme/context-types/governed-cast@1.0.0 +displayName: Team Roster +owner: Dana +--- +# Team + +- Dana — repository owner; approves releases. +- Sam — on-call engineer; triages CI failures. +- Priya — security reviewer; owns the release policy. diff --git a/examples/repo-agent/profiles/repo-defaults.profile.tfer b/examples/repo-agent/profiles/repo-defaults.profile.tfer new file mode 100644 index 0000000..a949eef --- /dev/null +++ b/examples/repo-agent/profiles/repo-defaults.profile.tfer @@ -0,0 +1,10 @@ +--- +schemaVersion: 3 +kind: profile +id: acme/profiles/repo-defaults@1.0.0 +displayName: Repository Defaults +description: Shared repository behavior; the status skill is sealed against override. +skills: + - ref: acme/skills/repository-status@1.0.0 + sealed: true +--- diff --git a/examples/repo-agent/skills/repository-status.skill.tfer b/examples/repo-agent/skills/repository-status.skill.tfer new file mode 100644 index 0000000..f60e3ba --- /dev/null +++ b/examples/repo-agent/skills/repository-status.skill.tfer @@ -0,0 +1,24 @@ +--- +schemaVersion: 3 +kind: skill +id: acme/skills/repository-status@1.0.0 +binds: acme/capabilities/repository-status@1.0.0 +displayName: Repository Status +description: Summarize repository health, delivered per invocation surface. +requiresTools: + - acme/tools/vault-reader@1.0.0 +requiresContextTypes: + - acme/context-types/cast-of-characters@1.0.0 +variants: + pipeline: + instructions: | + Emit strict JSON only. No prose. Never ask a clarifying question. + Mark every unavailable signal as an explicit null. + manual: + instructions: | + Explain the reasoning, surface risks conversationally, and offer the next + accountable action. Ask when a signal is ambiguous. + a2a: + instructions: | + Return attributed evidence for a calling agent. No user-facing framing. +--- diff --git a/examples/repo-agent/tools/vault-reader.tool.tfer b/examples/repo-agent/tools/vault-reader.tool.tfer new file mode 100644 index 0000000..0ce569e --- /dev/null +++ b/examples/repo-agent/tools/vault-reader.tool.tfer @@ -0,0 +1,10 @@ +--- +schemaVersion: 3 +kind: tool +id: acme/tools/vault-reader@1.0.0 +displayName: Vault Reader +description: >- + Reads notes from a knowledge vault at runtime. TypeFerence declares this + extern (its interface and, later, its auth/scope shape); the body is + implemented by the deployer (ADR-0017, ADR-0019 Door B). +--- diff --git a/go/cmd/typeference-lsp/main.go b/go/cmd/typeference-lsp/main.go new file mode 100644 index 0000000..dcf43e2 --- /dev/null +++ b/go/cmd/typeference-lsp/main.go @@ -0,0 +1,21 @@ +// Command typeference-lsp is a Language Server Protocol server for TypeFerence +// sources (.tfer and .yaml). It speaks JSON-RPC 2.0 over stdio and publishes +// per-document authoring diagnostics (see internal/lsp for the v0 scope). +package main + +import ( + "fmt" + "os" + + "github.com/buchk/TypeFerence/go/internal/lsp" +) + +// version is stamped by the release workflow via -ldflags "-X main.version=...". +var version = "dev" + +func main() { + if err := lsp.NewServer(version).Run(os.Stdin, os.Stdout); err != nil { + fmt.Fprintf(os.Stderr, "typeference-lsp: %s\n", err) + os.Exit(1) + } +} diff --git a/go/cmd/typeference/main.go b/go/cmd/typeference/main.go index 35537e3..f65a3ee 100644 --- a/go/cmd/typeference/main.go +++ b/go/cmd/typeference/main.go @@ -5,7 +5,10 @@ package main import ( + "bytes" + "encoding/json" "fmt" + "net/http" "os" "path/filepath" "slices" @@ -40,6 +43,8 @@ func run(args []string) int { code, err = inspect(args) case "diff": code, err = diff(args) + case "publish": + code, err = publish(args) case "eval": code, err = evalCommand(args) case "equivalence": @@ -90,7 +95,7 @@ func build(args []string) (int, error) { } else if v != "" { target = v } - ard, err := ardOptions(args) + ard, err := ardOptions(args, source) if err != nil { return 0, err } @@ -157,7 +162,7 @@ func diff(args []string) (int, error) { } defer os.RemoveAll(temp) - ard, err := ardOptions(args) + ard, err := ardOptions(args, source) if err != nil { return 0, err } @@ -205,6 +210,68 @@ func diff(args []string) (int, error) { return 0, nil } +// publish registers a compiled ARD catalog with a registry. It is the +// deployment edge, not the deterministic core (ADR-0018): registry lifecycle is +// disclaimed by core semantics, so this is an optional, side-effecting verb. +// Without --registry it is a dry run that only summarizes the catalog. +func publish(args []string) (int, error) { + dir, err := requiredArg(args, 1, "ard directory") + if err != nil { + return 0, err + } + catalogPath := filepath.Join(dir, "ai-catalog.json") + data, readErr := os.ReadFile(catalogPath) + if readErr != nil { + return 0, resource.Errorf("No ai-catalog.json under %s; build with --emit-ard first", dir) + } + var catalog struct { + Entries []struct { + Identifier string `json:"identifier"` + Type string `json:"type"` + } `json:"entries"` + } + if json.Unmarshal(data, &catalog) != nil { + return 0, resource.Errorf("Invalid ai-catalog.json: %s", catalogPath) + } + registry, err := option(args, "--registry") + if err != nil { + return 0, err + } + if registry == "" { + fmt.Printf("Dry run: %d catalog entries in %s (pass --registry to publish)\n", len(catalog.Entries), catalogPath) + for _, e := range catalog.Entries { + fmt.Printf(" %s %s\n", entryKind(e.Type), e.Identifier) + } + return 0, nil + } + resp, postErr := http.Post(registry, "application/json", bytes.NewReader(data)) + if postErr != nil { + return 0, resource.Errorf("Publish failed: %s", postErr) + } + defer resp.Body.Close() + if resp.StatusCode >= 300 { + return 1, resource.Errorf("Registry returned %s", resp.Status) + } + fmt.Printf("Published %d entries to %s (%s)\n", len(catalog.Entries), registry, resp.Status) + return 0, nil +} + +// entryKind maps an ARD entry media type to a short label. +func entryKind(mediaType string) string { + switch { + case strings.Contains(mediaType, "a2a-agent-card"): + return "a2a " + case strings.Contains(mediaType, "mcp-server"): + return "mcp " + case strings.Contains(mediaType, "source-package"): + return "source " + case strings.Contains(mediaType, "target-bundle"): + return "bundle " + default: + return "entry " + } +} + func evalCommand(args []string) (int, error) { source, err := requiredArg(args, 1, "source") if err != nil { @@ -306,7 +373,7 @@ func stringArr(values []string) jsonx.Arr { return arr } -func ardOptions(args []string) (*compile.ArdPublicationOptions, error) { +func ardOptions(args []string, source string) (*compile.ArdPublicationOptions, error) { publisherDomain, err := option(args, "--publisher-domain") if err != nil { return nil, err @@ -321,8 +388,21 @@ func ardOptions(args []string) (*compile.ArdPublicationOptions, error) { } allowUnsignedTrust := slices.Contains(args, "--allow-unsigned-trust") emitArd := slices.Contains(args, "--emit-ard") + // A project manifest (typeference.yaml) with a publisher makes ARD emission + // the default and supplies the publisher domain, so `compile -> pushable + // ai-catalog.json` needs no flags (ADR-0018). + project, projErr := resource.LoadProject(source) + if projErr != nil { + return nil, projErr + } + if project != nil && strings.TrimSpace(project.Publisher) != "" { + emitArd = true + if publisherDomain == "" { + publisherDomain = project.Publisher + } + } if emitArd && publisherDomain == "" { - return nil, resource.Errorf("--emit-ard requires --publisher-domain") + return nil, resource.Errorf("--emit-ard requires --publisher-domain (or a `publisher` in typeference.yaml)") } if !emitArd && publisherDomain != "" { return nil, resource.Errorf("--publisher-domain requires --emit-ard") @@ -385,6 +465,9 @@ Commands: [--emit-ard --publisher-domain example.com] [--trust-config path] [--trust-signatures signatures.json] [--json] [--allow-unsigned-trust] + typeference publish [--registry ] + (deployment edge, not core: summarizes the compiled ARD catalog; with + --registry POSTs it to a registry. A dry run without --registry.) typeference eval --scenarios [--live] [--model id] [--out dir] (dry run by default: validates scenarios and emits exact request payloads without calling any API; --live reads ANTHROPIC_API_KEY) diff --git a/go/internal/compile/ard.go b/go/internal/compile/ard.go index 78a9259..b88bb0a 100644 --- a/go/internal/compile/ard.go +++ b/go/internal/compile/ard.go @@ -36,6 +36,19 @@ func writeArdCatalog( return resource.Errorf("Source directory not found: %s", source) } sourceName := urnSegment(filepath.Base(strings.TrimRight(sourceAbs, `\/`))) + sourceVersion := "1.0.0" + project, projErr := resource.LoadProject(source) + if projErr != nil { + return projErr + } + if project != nil { + if strings.TrimSpace(project.Name) != "" { + sourceName = urnSegment(project.Name) + } + if strings.TrimSpace(project.Version) != "" { + sourceVersion = project.Version + } + } sourceIdentifier := "urn:air:" + publisherDomain + ":typeference:source:" + sourceName sourceHash, err := HashDirectory(source) if err != nil { @@ -55,7 +68,7 @@ func writeArdCatalog( {K: "displayName", V: jsonx.Str("TypeFerence source package: " + sourceName)}, {K: "type", V: jsonx.Str("application/vnd.typeference.source-package+json")}, {K: "description", V: jsonx.Str("Canonical typed source package for validation, audit, and reproducible compilation.")}, - {K: "version", V: jsonx.Str("1.0.0")}, + {K: "version", V: jsonx.Str(sourceVersion)}, {K: "data", V: jsonx.Obj{ {K: "schemaVersion", V: jsonx.Num("1")}, {K: "digest", V: jsonx.Str(sourceDigest)}, @@ -169,6 +182,60 @@ func writeArdCatalog( } } + // Discovery entries: for each agent that exposes a capability, an A2A Agent + // Card and an MCP server, both in the official media types a registry + // indexes, embedding the resource as `data` (value-or-reference honored). + // There is no proprietary catalog entry — the catalog is pure discovery; the + // fulfillment (instruction templates, held context) rides the MCP manifest's + // x-typeference extension, i.e. the deployable server artifact (ADR-0018). + for _, agent := range agents { + if len(agent.ExposedSkills()) == 0 { + continue + } + slug := resolve.Leaf(agent.ID) + version := agent.ID[strings.LastIndex(agent.ID, "@")+1:] + names := []string{} + for _, skill := range exposedInOrder(agent) { + names = append(names, skill.DispatchName) + } + manifest := jsonx.Obj{ + {K: "identity", V: jsonx.Str("https://" + publisherDomain)}, + {K: "identityType", V: jsonx.Str("https")}, + {K: "provenance", V: jsonx.Arr{jsonx.Obj{ + {K: "relation", V: jsonx.Str("derivedFrom")}, + {K: "sourceId", V: jsonx.Str(sourceIdentifier)}, + {K: "sourceDigest", V: jsonx.Str(sourceDigest)}, + }}}, + } + meta := jsonx.Obj{ + {K: "generatedBy", V: jsonx.Str("TypeFerence")}, + {K: "sourceDigest", V: jsonx.Str(sourceDigest)}, + {K: "sourceIdentifier", V: jsonx.Str(sourceIdentifier)}, + } + entries = append(entries, jsonx.Obj{ + {K: "identifier", V: jsonx.Str("urn:air:" + publisherDomain + ":typeference:a2a:" + slug)}, + {K: "displayName", V: jsonx.Str(agent.DisplayName + " (A2A agent card)")}, + {K: "type", V: jsonx.Str("application/a2a-agent-card+json")}, + {K: "description", V: jsonx.Str("A2A Agent Card for " + agent.DisplayName + ".")}, + {K: "capabilities", V: stringArr(names)}, + {K: "version", V: jsonx.Str(version)}, + {K: "data", V: a2aCardValue(agent, publisherDomain)}, + {K: "metadata", V: meta}, + {K: "trustManifest", V: manifest}, + }) + entries = append(entries, jsonx.Obj{ + {K: "identifier", V: jsonx.Str("urn:air:" + publisherDomain + ":typeference:mcp:" + slug)}, + {K: "displayName", V: jsonx.Str(agent.DisplayName + " (MCP tools)")}, + {K: "type", V: jsonx.Str("application/mcp-server+json")}, + {K: "description", V: jsonx.Str("MCP tool manifest for " + agent.DisplayName + ".")}, + {K: "capabilities", V: stringArr(names)}, + {K: "version", V: jsonx.Str(version)}, + {K: "data", V: mcpManifestValue(agent)}, + {K: "metadata", V: meta}, + {K: "trustManifest", V: manifest}, + }) + } + for _, key := range signatureKeys { if !signedIdentifiers[key] { return resource.Errorf("Trust signature identifier does not match a configured catalog entry: %s", key) @@ -293,6 +360,141 @@ func buildTrustManifest( return manifest.obj(), nil } +// writeDiscoveryCards emits, per agent that exposes a capability, two artifacts +// in shapes external tooling actually consumes (ADR-0018): +// +// - .agent-card.json — an A2A Agent Card (a2a-protocol.org), the unit an +// agent finder discovers. TypeFerence is definition-layer, so the required +// service-endpoint `url` is templated by convention (https:///a2a/ +// ); the deployer serves the A2A endpoint there or overrides it. +// - .mcp.json — an MCP tool manifest (modelcontextprotocol.io), directly +// loadable by an MCP server; each exposed capability becomes an MCP tool with +// its input/output schemas as JSON Schema objects. +func writeDiscoveryCards(ardRoot string, agents []*resolve.ResolvedAgent, publisherDomain string, written *[]string) error { + for _, agent := range agents { + if len(agent.ExposedSkills()) == 0 { + continue + } + slug := resolve.Leaf(agent.ID) + if err := writeFile(filepath.Join(ardRoot, slug+".agent-card.json"), jsonx.Indented(a2aCardValue(agent, publisherDomain))+"\n", written); err != nil { + return err + } + if err := writeFile(filepath.Join(ardRoot, slug+".mcp.json"), jsonx.Indented(mcpManifestValue(agent))+"\n", written); err != nil { + return err + } + } + return nil +} + +// exposedInOrder returns an agent's exposed skills sorted by dispatch name. +func exposedInOrder(agent *resolve.ResolvedAgent) []resolve.ResolvedSkill { + exposed := agent.ExposedSkills() + sort.Slice(exposed, func(i, j int) bool { return exposed[i].DispatchName < exposed[j].DispatchName }) + return exposed +} + +// a2aCardValue builds an A2A Agent Card (a2a-protocol.org) for an agent's +// exposed capabilities. The required service-endpoint url is templated by +// convention since TypeFerence is definition-layer. +func a2aCardValue(agent *resolve.ResolvedAgent, publisherDomain string) jsonx.Obj { + slug := resolve.Leaf(agent.ID) + skills := jsonx.Arr{} + for _, skill := range exposedInOrder(agent) { + skills = append(skills, jsonx.Obj{ + {K: "id", V: jsonx.Str(skill.DispatchName)}, + {K: "name", V: jsonx.Str(resolve.Leaf(skill.CapabilityID))}, + {K: "description", V: jsonx.Str(skill.Description)}, + {K: "tags", V: jsonx.Arr{jsonx.Str("typeference")}}, + }) + } + return jsonx.Obj{ + {K: "protocolVersion", V: jsonx.Str("0.3.0")}, + {K: "name", V: jsonx.Str(agent.DisplayName)}, + {K: "description", V: jsonx.Str(agent.Description)}, + {K: "version", V: jsonx.Str(agent.ID[strings.LastIndex(agent.ID, "@")+1:])}, + {K: "url", V: jsonx.Str("https://" + publisherDomain + "/a2a/" + slug)}, + {K: "preferredTransport", V: jsonx.Str("JSONRPC")}, + {K: "capabilities", V: jsonx.Obj{ + {K: "streaming", V: jsonx.Bool(false)}, + {K: "pushNotifications", V: jsonx.Bool(false)}, + }}, + {K: "defaultInputModes", V: jsonx.Arr{jsonx.Str("application/json")}}, + {K: "defaultOutputModes", V: jsonx.Arr{jsonx.Str("application/json")}}, + {K: "provider", V: jsonx.Obj{ + {K: "organization", V: jsonx.Str(publisherDomain)}, + {K: "url", V: jsonx.Str("https://" + publisherDomain)}, + }}, + {K: "skills", V: skills}, + } +} + +// mcpManifestValue builds a full MCP tool manifest for an agent: standard MCP +// tools plus a top-level x-typeference extension carrying the agent's held +// context. Generic MCP clients read the standard fields and ignore x-*; a +// TypeFerence-aware server also uses the extension to fulfill calls. +func mcpManifestValue(agent *resolve.ResolvedAgent) jsonx.Obj { + manifest := jsonx.Obj{{K: "tools", V: mcpToolsValue(agent)}} + if len(agent.ContextObjects) > 0 { + manifest = append(manifest, jsonx.Member{K: "x-typeference", V: jsonx.Obj{ + {K: "context", V: callableContext(agent.ContextObjects)}, + }}) + } + return manifest +} + +// mcpToolsValue builds the MCP tool array (modelcontextprotocol.io) for an +// agent's exposed capabilities, with schemas as JSON Schema objects. Each tool +// carries an x-typeference extension with the fulfillment a server needs — the +// a2a instructions template, every declared variant, and required tools — which +// generic MCP clients ignore. +func mcpToolsValue(agent *resolve.ResolvedAgent) jsonx.Arr { + tools := jsonx.Arr{} + for _, skill := range exposedInOrder(agent) { + ext := jsonx.Obj{ + {K: "instructionsTemplate", V: jsonx.Str(skill.InstructionsFor("a2a"))}, + {K: "requiresTools", V: stringArr(skill.RequiresTools)}, + } + if len(skill.Variants) > 0 { + variants := jsonx.Obj{} + for _, mode := range sortedModes(skill.Variants) { + variants = append(variants, jsonx.Member{K: mode, V: jsonx.Str(skill.Variants[mode])}) + } + ext = append(ext, jsonx.Member{K: "variants", V: variants}) + } + tools = append(tools, jsonx.Obj{ + {K: "name", V: jsonx.Str(skill.DispatchName)}, + {K: "description", V: jsonx.Str(skill.Description)}, + {K: "inputSchema", V: schemaObject(skill.InputSchema)}, + {K: "outputSchema", V: schemaObject(skill.OutputSchema)}, + {K: "x-typeference", V: ext}, + }) + } + return tools +} + +// schemaObject parses a capability's JSON Schema string into an object so it can +// be embedded (MCP requires inputSchema to be a JSON Schema object, not a +// string); a malformed schema falls back to an open object. +func schemaObject(schema string) jsonx.Value { + if v, err := jsonx.Parse(schema); err == nil { + return v + } + return jsonx.Obj{{K: "type", V: jsonx.Str("object")}} +} + +// callableContext renders an agent's held context objects for a callable card. +func callableContext(objs []resolve.ResolvedContextRef) jsonx.Value { + arr := jsonx.Arr{} + for _, ref := range objs { + arr = append(arr, jsonx.Obj{ + {K: "id", V: jsonx.Str(ref.ID)}, + {K: "contextType", V: jsonx.Str(ref.ContextType)}, + {K: "content", V: jsonx.Str(ref.Content)}, + }) + } + return arr +} + func metadataValueJSON(value trust.MetadataValue) jsonx.Value { switch v := value.(type) { case nil: diff --git a/go/internal/compile/bundle.go b/go/internal/compile/bundle.go index 6384bff..8e4a8de 100644 --- a/go/internal/compile/bundle.go +++ b/go/internal/compile/bundle.go @@ -1,6 +1,8 @@ package compile import ( + "sort" + "github.com/buchk/TypeFerence/go/internal/jsonx" "github.com/buchk/TypeFerence/go/internal/resolve" ) @@ -22,7 +24,7 @@ func bundleValue(agent *resolve.ResolvedAgent) jsonx.Value { for _, skill := range agent.Skills { skills = append(skills, skillValue(skill)) } - return jsonx.Obj{ + obj := jsonx.Obj{ {K: "id", V: jsonx.Str(agent.ID)}, {K: "displayName", V: jsonx.Str(agent.DisplayName)}, {K: "description", V: jsonx.Str(agent.Description)}, @@ -32,13 +34,27 @@ func bundleValue(agent *resolve.ResolvedAgent) jsonx.Value { {K: "slots", V: slots}, {K: "workingNorms", V: stringArr(agent.WorkingNorms)}, {K: "contextFiles", V: stringArr(agent.ContextFiles)}, - {K: "skills", V: skills}, - {K: "provenance", V: provenanceValue(agent.Provenance)}, } + // Typed context held by id. Absent when the agent holds none, so bundles + // that predate reference-by-id context are unchanged (ADR-0013). + if len(agent.ContextObjects) > 0 { + context := jsonx.Arr{} + for _, ref := range agent.ContextObjects { + context = append(context, jsonx.Obj{ + {K: "id", V: jsonx.Str(ref.ID)}, + {K: "contextType", V: jsonx.Str(ref.ContextType)}, + {K: "content", V: jsonx.Str(ref.Content)}, + }) + } + obj = append(obj, jsonx.Member{K: "context", V: context}) + } + obj = append(obj, jsonx.Member{K: "skills", V: skills}) + obj = append(obj, jsonx.Member{K: "provenance", V: provenanceValue(agent.Provenance)}) + return obj } func skillValue(skill resolve.ResolvedSkill) jsonx.Value { - return jsonx.Obj{ + obj := jsonx.Obj{ {K: "dispatchName", V: jsonx.Str(skill.DispatchName)}, {K: "capabilityId", V: jsonx.Str(skill.CapabilityID)}, {K: "implementationId", V: jsonx.Str(skill.ImplementationID)}, @@ -47,8 +63,23 @@ func skillValue(skill resolve.ResolvedSkill) jsonx.Value { {K: "inputSchema", V: jsonx.Str(skill.InputSchema)}, {K: "outputSchema", V: jsonx.Str(skill.OutputSchema)}, {K: "contextFiles", V: stringArr(skill.ContextFiles)}, - {K: "provenance", V: provenanceValue(skill.Provenance)}, } + // A multimodal skill also emits its per-mode renderings. This member is + // absent for unimodal skills, so their bundle output is unchanged (ADR-0012). + if len(skill.Variants) > 0 { + modes := make([]string, 0, len(skill.Variants)) + for mode := range skill.Variants { + modes = append(modes, mode) + } + sort.Strings(modes) + variants := jsonx.Obj{} + for _, mode := range modes { + variants = append(variants, jsonx.Member{K: mode, V: jsonx.Str(skill.Variants[mode])}) + } + obj = append(obj, jsonx.Member{K: "variants", V: variants}) + } + obj = append(obj, jsonx.Member{K: "provenance", V: provenanceValue(skill.Provenance)}) + return obj } // provenanceJSON renders the canonical indented provenance.json. diff --git a/go/internal/compile/callable_card_test.go b/go/internal/compile/callable_card_test.go new file mode 100644 index 0000000..c9ab699 --- /dev/null +++ b/go/internal/compile/callable_card_test.go @@ -0,0 +1,55 @@ +package compile + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func writeSrc(t *testing.T, root, name, content string) { + t.Helper() + if err := os.WriteFile(filepath.Join(root, name), []byte(content), 0o644); err != nil { + t.Fatal(err) + } +} + +func buildCatalog(t *testing.T, exposed bool) string { + t.Helper() + src := t.TempDir() + vis := "" + if exposed { + vis = "visibility: exposed\n" + } + writeSrc(t, src, "cap.yaml", "schemaVersion: 3\nkind: capability\nid: acme/cap/c@1.0.0\n"+vis) + writeSrc(t, src, "skill.yaml", "schemaVersion: 3\nkind: skill\nid: acme/skills/s@1.0.0\nbinds: acme/cap/c@1.0.0\ninstructions: do it\n") + writeSrc(t, src, "agent.yaml", "schemaVersion: 3\nkind: agent\nid: acme/agent@1.0.0\nskills:\n - ref: acme/skills/s@1.0.0\n") + out := t.TempDir() + targets, err := ParseTargets("neutral") + if err != nil { + t.Fatal(err) + } + if _, err := Build(src, out, targets, &ArdPublicationOptions{PublisherDomain: "acme.example"}); err != nil { + t.Fatal(err) + } + raw, err := os.ReadFile(filepath.Join(out, "ard", "ai-catalog.json")) + if err != nil { + t.Fatal(err) + } + return string(raw) +} + +func TestCatalogHasNoProprietaryCard(t *testing.T) { + catalog := buildCatalog(t, true) + if strings.Contains(catalog, "callable-card") || strings.Contains(catalog, "typeference:callable:") { + t.Error("the ai-catalog.json must carry no proprietary callable card; discovery is standard-typed") + } +} + +func TestCatalogUsesStandardDiscoveryTypes(t *testing.T) { + // jsonx escapes the '+' in a media type, so match without the "+json" suffix. + catalog := buildCatalog(t, true) + if !strings.Contains(catalog, "application/a2a-agent-card") || !strings.Contains(catalog, "application/mcp-server") { + t.Error("an exposed agent should produce standard A2A and MCP catalog entries") + } +} diff --git a/go/internal/compile/compiler.go b/go/internal/compile/compiler.go index 5c530fc..0f27cf4 100644 --- a/go/internal/compile/compiler.go +++ b/go/internal/compile/compiler.go @@ -170,6 +170,9 @@ func Build(source, output string, targets []Target, ard *ArdPublicationOptions) configuration, signatures, signatureKeys, ard.AllowUnsignedTrust, &written); err != nil { return nil, err } + if err := writeDiscoveryCards(ardRoot, agents, ard.PublisherDomain, &written); err != nil { + return nil, err + } } sort.Strings(written) return written, nil @@ -200,7 +203,9 @@ func writeTarget(target Target, root string, agent *resolve.ResolvedAgent, writt slug := resolve.Leaf(agent.ID) switch target { case Neutral: - if err := writeFile(filepath.Join(root, slug, "AGENTS.md"), renderInstructions(agent), written); err != nil { + // Canonical bundle: an index doc plus a SKILL.md per skill, fanning out + // one SKILL..md per variant (ADR-0012). + if err := writeFile(filepath.Join(root, slug, "AGENTS.md"), renderInstructions(agent, false, ""), written); err != nil { return err } if err := writeFile(filepath.Join(root, slug, "bundle.json"), bundleJSON(agent)+"\n", written); err != nil { @@ -210,16 +215,18 @@ func writeTarget(target Target, root string, agent *resolve.ResolvedAgent, writt return err } for _, skill := range agent.Skills { - if err := writeFile(filepath.Join(root, slug, "skills", skillSlug(skill), "SKILL.md"), renderSkill(skill), written); err != nil { + if err := writeSkillFiles(filepath.Join(root, slug, "skills", skillSlug(skill)), skill, "", true, written); err != nil { return err } } case Codex: - if err := writeFile(filepath.Join(root, slug, "AGENTS.md"), renderInstructions(agent), written); err != nil { + // Interactive coding surface: an index doc plus one SKILL.md rendering the + // manual variant. + if err := writeFile(filepath.Join(root, slug, "AGENTS.md"), renderInstructions(agent, false, ""), written); err != nil { return err } for _, skill := range agent.Skills { - if err := writeFile(filepath.Join(root, slug, ".agents", "skills", skillSlug(skill), "SKILL.md"), renderSkill(skill), written); err != nil { + if err := writeSkillFiles(filepath.Join(root, slug, ".agents", "skills", skillSlug(skill)), skill, variantForTarget(target), false, written); err != nil { return err } } @@ -231,10 +238,12 @@ func writeTarget(target Target, root string, agent *resolve.ResolvedAgent, writt return err } case Copilot: - if err := writeFile(filepath.Join(root, slug, ".github", "copilot-instructions.md"), renderInstructions(agent), written); err != nil { + // No per-skill file: instructions are inlined in the manual variant. + instructions := renderInstructions(agent, true, variantForTarget(target)) + if err := writeFile(filepath.Join(root, slug, ".github", "copilot-instructions.md"), instructions, written); err != nil { return err } - agentMD := "---\nname: " + slug + "\ndescription: " + escapeYAML(agent.Description) + "\n---\n\n" + renderInstructions(agent) + agentMD := "---\nname: " + slug + "\ndescription: " + escapeYAML(agent.Description) + "\n---\n\n" + instructions if err := writeFile(filepath.Join(root, slug, ".github", "agents", slug+".agent.md"), agentMD, written); err != nil { return err } @@ -242,10 +251,11 @@ func writeTarget(target Target, root string, agent *resolve.ResolvedAgent, writt return err } case Cursor: - if err := writeFile(filepath.Join(root, slug, "AGENTS.md"), renderInstructions(agent), written); err != nil { + instructions := renderInstructions(agent, true, variantForTarget(target)) + if err := writeFile(filepath.Join(root, slug, "AGENTS.md"), instructions, written); err != nil { return err } - rule := "---\ndescription: " + escapeYAML(agent.Description) + "\nglobs:\nalwaysApply: true\n---\n\n" + renderInstructions(agent) + rule := "---\ndescription: " + escapeYAML(agent.Description) + "\nglobs:\nalwaysApply: true\n---\n\n" + instructions if err := writeFile(filepath.Join(root, slug, ".cursor", "rules", slug+".mdc"), rule, written); err != nil { return err } @@ -256,7 +266,40 @@ func writeTarget(target Target, root string, agent *resolve.ResolvedAgent, writt return nil } -func renderInstructions(agent *resolve.ResolvedAgent) string { +// variantForTarget is the invocation-mode variant a target renders for a +// multimodal skill: interactive/human surfaces get "manual"; the neutral +// (canonical) bundle keeps the default and fans every variant out (ADR-0012). +func variantForTarget(t Target) string { + switch t { + case Copilot, Cursor, Codex: + return "manual" + default: + return "" + } +} + +// writeSkillFiles writes a skill's SKILL.md (rendered in the given variant, or +// the default when variant is empty) and, when fanout is set, one SKILL..md +// per variant (ADR-0012). Shared by every target that emits per-skill files. +func writeSkillFiles(dir string, skill resolve.ResolvedSkill, variant string, fanout bool, written *[]string) error { + primary := skill.Instructions + if variant != "" { + primary = skill.InstructionsFor(variant) + } + if err := writeFile(filepath.Join(dir, "SKILL.md"), renderSkillWith(skill, primary), written); err != nil { + return err + } + if fanout { + for _, mode := range sortedModes(skill.Variants) { + if err := writeFile(filepath.Join(dir, "SKILL."+mode+".md"), renderSkillWith(skill, skill.Variants[mode]), written); err != nil { + return err + } + } + } + return nil +} + +func renderInstructions(agent *resolve.ResolvedAgent, inlineInstructions bool, variant string) string { var b strings.Builder b.WriteString("# " + agent.DisplayName + "\n\n" + agent.Description + "\n\n") if len(agent.WorkingNorms) > 0 { @@ -273,24 +316,57 @@ func renderInstructions(agent *resolve.ResolvedAgent) string { } b.WriteString("\n") } + // Held context objects are inlined so the compiled agent actually contains + // its context, not just a reference to it (ADR-0013). + if len(agent.ContextObjects) > 0 { + b.WriteString("## Context\n\n") + for _, ref := range agent.ContextObjects { + heading := ref.DisplayName + if strings.TrimSpace(heading) == "" { + heading = ref.ID + } + b.WriteString("### " + heading + "\n\n") + if content := strings.TrimSpace(ref.Content); content != "" { + b.WriteString(content + "\n\n") + } + } + } b.WriteString("## Available skills\n\n") for _, skill := range agent.Skills { b.WriteString("- `" + skill.DispatchName + "`: " + skill.Description + "\n") + // Targets with no separate SKILL.md inline the instructions here, in the + // surface's variant, so those agents actually receive skill behavior + // rather than just a name (ADR-0012). + if inlineInstructions { + b.WriteString("\n" + strings.TrimSpace(skill.InstructionsFor(variant)) + "\n\n") + } } b.WriteString("\n") return b.String() } -func renderSkill(skill resolve.ResolvedSkill) string { +// renderSkillWith renders a skill's SKILL.md using the given instructions, so a +// per-variant file can carry that mode's rendering (ADR-0012). +func renderSkillWith(skill resolve.ResolvedSkill, instructions string) string { lines := make([]string, len(skill.ContextFiles)) for i, file := range skill.ContextFiles { lines[i] = "- `" + file + "`" } return "---\nname: " + skillSlug(skill) + "\ndescription: " + escapeYAML(skill.Description) + "\n---\n\n" + - strings.TrimSpace(skill.Instructions) + "\n\n## Context loaded on invocation\n\n" + + strings.TrimSpace(instructions) + "\n\n## Context loaded on invocation\n\n" + strings.Join(lines, "\n") + "\n" } +// sortedModes returns a variant map's mode names in canonical order. +func sortedModes(variants map[string]string) []string { + modes := make([]string, 0, len(variants)) + for mode := range variants { + modes = append(modes, mode) + } + sort.Strings(modes) + return modes +} + func skillSlug(skill resolve.ResolvedSkill) string { return resolve.Leaf(skill.CapabilityID) } func escapeYAML(value string) string { diff --git a/go/internal/compile/context_emit_test.go b/go/internal/compile/context_emit_test.go new file mode 100644 index 0000000..ef26186 --- /dev/null +++ b/go/internal/compile/context_emit_test.go @@ -0,0 +1,44 @@ +package compile + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestBundleEmitsHeldContext(t *testing.T) { + src := t.TempDir() + writeSrc(t, src, "ct.yaml", "schemaVersion: 3\nkind: contextType\nid: acme/ct/cast@1.0.0\n") + writeSrc(t, src, "note.yaml", "schemaVersion: 3\nkind: context\nid: acme/notes/n@1.0.0\ncontextType: acme/ct/cast@1.0.0\n") + writeSrc(t, src, "agent.yaml", "schemaVersion: 3\nkind: agent\nid: acme/agent@1.0.0\ncontext:\n - acme/notes/n@1.0.0\n") + out := t.TempDir() + targets, _ := ParseTargets("neutral") + if _, err := Build(src, out, targets, nil); err != nil { + t.Fatal(err) + } + bundle, err := os.ReadFile(filepath.Join(out, "neutral", "agent", "bundle.json")) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(bundle), `"context"`) || !strings.Contains(string(bundle), "acme/notes/n@1.0.0") { + t.Errorf("bundle should list held context objects:\n%s", bundle) + } + if !strings.Contains(string(bundle), "acme/ct/cast@1.0.0") { + t.Errorf("held context should carry its contextType") + } +} + +func TestBundleOmitsContextWhenNoneHeld(t *testing.T) { + src := t.TempDir() + writeSrc(t, src, "agent.yaml", "schemaVersion: 3\nkind: agent\nid: acme/agent@1.0.0\n") + out := t.TempDir() + targets, _ := ParseTargets("neutral") + if _, err := Build(src, out, targets, nil); err != nil { + t.Fatal(err) + } + bundle, _ := os.ReadFile(filepath.Join(out, "neutral", "agent", "bundle.json")) + if strings.Contains(string(bundle), `"context"`) { + t.Errorf("an agent holding no context must not emit a context member:\n%s", bundle) + } +} diff --git a/go/internal/compile/discovery_test.go b/go/internal/compile/discovery_test.go new file mode 100644 index 0000000..eb22205 --- /dev/null +++ b/go/internal/compile/discovery_test.go @@ -0,0 +1,103 @@ +package compile + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" +) + +func buildDiscovery(t *testing.T, exposed bool) string { + t.Helper() + src := t.TempDir() + vis := "" + if exposed { + vis = "visibility: exposed\n" + } + writeSrc(t, src, "cap.yaml", "schemaVersion: 3\nkind: capability\nid: acme/cap/status@1.0.0\ndisplayName: Status\ninputSchema: '{\"type\":\"object\",\"properties\":{\"focus\":{\"type\":\"string\"}},\"additionalProperties\":false}'\n"+vis) + writeSrc(t, src, "skill.yaml", "schemaVersion: 3\nkind: skill\nid: acme/skills/s@1.0.0\nbinds: acme/cap/status@1.0.0\ninstructions: do it\ninputSchema: '{\"type\":\"object\",\"properties\":{\"focus\":{\"type\":\"string\"}},\"additionalProperties\":false}'\n") + writeSrc(t, src, "agent.yaml", "schemaVersion: 3\nkind: agent\nid: acme/agent@1.0.0\ndisplayName: Acme Agent\nskills:\n - ref: acme/skills/s@1.0.0\n") + out := t.TempDir() + targets, _ := ParseTargets("neutral") + if _, err := Build(src, out, targets, &ArdPublicationOptions{PublisherDomain: "acme.example"}); err != nil { + t.Fatal(err) + } + return filepath.Join(out, "ard") +} + +func TestA2ACardAndMCPManifestEmitted(t *testing.T) { + ard := buildDiscovery(t, true) + + var card map[string]any + raw, err := os.ReadFile(filepath.Join(ard, "agent.agent-card.json")) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(raw, &card); err != nil { + t.Fatalf("agent card is not valid JSON: %v", err) + } + for _, k := range []string{"protocolVersion", "name", "url", "skills"} { + if _, ok := card[k]; !ok { + t.Errorf("A2A agent card missing required field %q", k) + } + } + if len(card["skills"].([]any)) != 1 { + t.Errorf("expected one A2A skill for the exposed capability") + } + + var mcp struct { + Tools []struct { + Name string `json:"name"` + InputSchema map[string]any `json:"inputSchema"` + } `json:"tools"` + } + raw, err = os.ReadFile(filepath.Join(ard, "agent.mcp.json")) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(raw, &mcp); err != nil { + t.Fatalf("mcp manifest is not valid JSON: %v", err) + } + if len(mcp.Tools) != 1 || mcp.Tools[0].Name != "agent.status" { + t.Fatalf("expected one MCP tool named agent.status, got %+v", mcp.Tools) + } + // inputSchema must be a JSON Schema object, not a string. + if mcp.Tools[0].InputSchema["type"] != "object" { + t.Errorf("MCP tool inputSchema must be a JSON Schema object") + } +} + +func TestCatalogHasOfficialTypedEntries(t *testing.T) { + ard := buildDiscovery(t, true) + var catalog struct { + Entries []struct { + Type string `json:"type"` + } `json:"entries"` + } + raw, err := os.ReadFile(filepath.Join(ard, "ai-catalog.json")) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(raw, &catalog); err != nil { + t.Fatal(err) + } + types := map[string]bool{} + for _, e := range catalog.Entries { + types[e.Type] = true + } + for _, want := range []string{"application/a2a-agent-card+json", "application/mcp-server+json"} { + if !types[want] { + t.Errorf("ai-catalog.json is missing an official %q entry a registry would index", want) + } + } +} + +func TestNoDiscoveryCardsWithoutExposure(t *testing.T) { + ard := buildDiscovery(t, false) + if _, err := os.Stat(filepath.Join(ard, "agent.agent-card.json")); err == nil { + t.Error("an agent exposing nothing must not emit an A2A card") + } + if _, err := os.Stat(filepath.Join(ard, "agent.mcp.json")); err == nil { + t.Error("an agent exposing nothing must not emit an MCP manifest") + } +} diff --git a/go/internal/compile/fanout_test.go b/go/internal/compile/fanout_test.go new file mode 100644 index 0000000..40d0984 --- /dev/null +++ b/go/internal/compile/fanout_test.go @@ -0,0 +1,53 @@ +package compile + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestNeutralVariantFanout(t *testing.T) { + src := t.TempDir() + writeSrc(t, src, "cap.yaml", "schemaVersion: 3\nkind: capability\nid: acme/cap/c@1.0.0\n") + writeSrc(t, src, "skill.yaml", "schemaVersion: 3\nkind: skill\nid: acme/skills/s@1.0.0\nbinds: acme/cap/c@1.0.0\nvariants:\n pipeline:\n instructions: strict\n a2a:\n instructions: attributed\n") + writeSrc(t, src, "agent.yaml", "schemaVersion: 3\nkind: agent\nid: acme/agent@1.0.0\nskills:\n - ref: acme/skills/s@1.0.0\n") + out := t.TempDir() + targets, err := ParseTargets("neutral") + if err != nil { + t.Fatal(err) + } + if _, err := Build(src, out, targets, nil); err != nil { + t.Fatal(err) + } + base := filepath.Join(out, "neutral", "agent", "skills", "c") + for _, name := range []string{"SKILL.md", "SKILL.pipeline.md", "SKILL.a2a.md"} { + if _, err := os.Stat(filepath.Join(base, name)); err != nil { + t.Errorf("expected %s to be emitted: %v", name, err) + } + } + body, err := os.ReadFile(filepath.Join(base, "SKILL.a2a.md")) + if err != nil || !strings.Contains(string(body), "attributed") { + t.Errorf("SKILL.a2a.md should carry the a2a variant's instructions") + } +} + +func TestUnimodalSkillNoFanout(t *testing.T) { + src := t.TempDir() + writeSrc(t, src, "cap.yaml", "schemaVersion: 3\nkind: capability\nid: acme/cap/c@1.0.0\n") + writeSrc(t, src, "skill.yaml", "schemaVersion: 3\nkind: skill\nid: acme/skills/s@1.0.0\nbinds: acme/cap/c@1.0.0\ninstructions: do it\n") + writeSrc(t, src, "agent.yaml", "schemaVersion: 3\nkind: agent\nid: acme/agent@1.0.0\nskills:\n - ref: acme/skills/s@1.0.0\n") + out := t.TempDir() + targets, _ := ParseTargets("neutral") + if _, err := Build(src, out, targets, nil); err != nil { + t.Fatal(err) + } + base := filepath.Join(out, "neutral", "agent", "skills", "c") + entries, err := os.ReadDir(base) + if err != nil { + t.Fatal(err) + } + if len(entries) != 1 || entries[0].Name() != "SKILL.md" { + t.Errorf("unimodal skill should emit only SKILL.md, got %v", entries) + } +} diff --git a/go/internal/compile/parity_test.go b/go/internal/compile/parity_test.go index 43afa9f..541951f 100644 --- a/go/internal/compile/parity_test.go +++ b/go/internal/compile/parity_test.go @@ -17,11 +17,10 @@ func repoPath(t *testing.T, parts ...string) string { return filepath.Join(append([]string{root}, parts...)...) } -// TestHelioParityWithReferenceImplementation compiles the shared example and -// byte-compares every target tree against the committed output of the C# -// reference implementation. This is the cross-implementation contract: same -// input, byte-identical artifacts. -func TestHelioParityWithReferenceImplementation(t *testing.T) { +// TestHelioParityWithCommittedOutput compiles the shared example and +// byte-compares every target tree against the committed dist/ reference. This is +// the determinism contract: same input, byte-identical artifacts (ADR-0014). +func TestHelioParityWithCommittedOutput(t *testing.T) { source := repoPath(t, "examples", "helio") if _, err := os.Stat(source); err != nil { t.Skipf("examples/helio not available: %v", err) diff --git a/go/internal/compile/target_variant_test.go b/go/internal/compile/target_variant_test.go new file mode 100644 index 0000000..1170cb2 --- /dev/null +++ b/go/internal/compile/target_variant_test.go @@ -0,0 +1,51 @@ +package compile + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestTargetsSelectSurfaceVariant(t *testing.T) { + src := t.TempDir() + writeSrc(t, src, "cap.yaml", "schemaVersion: 3\nkind: capability\nid: acme/cap/c@1.0.0\n") + writeSrc(t, src, "skill.yaml", "schemaVersion: 3\nkind: skill\nid: acme/skills/s@1.0.0\nbinds: acme/cap/c@1.0.0\nvariants:\n pipeline:\n instructions: PIPELINE_TEXT\n manual:\n instructions: MANUAL_TEXT\n") + writeSrc(t, src, "agent.yaml", "schemaVersion: 3\nkind: agent\nid: acme/agent@1.0.0\nskills:\n - ref: acme/skills/s@1.0.0\n") + out := t.TempDir() + targets, err := ParseTargets("all") + if err != nil { + t.Fatal(err) + } + if _, err := Build(src, out, targets, nil); err != nil { + t.Fatal(err) + } + read := func(parts ...string) string { + b, _ := os.ReadFile(filepath.Join(append([]string{out}, parts...)...)) + return string(b) + } + + // Copilot has no per-skill file, so it inlines the manual variant. + cop := read("copilot", "agent", ".github", "copilot-instructions.md") + if !strings.Contains(cop, "MANUAL_TEXT") || strings.Contains(cop, "PIPELINE_TEXT") { + t.Errorf("copilot should inline the manual variant, not pipeline:\n%s", cop) + } + // Cursor likewise inlines manual. + cur := read("cursor", "agent", "AGENTS.md") + if !strings.Contains(cur, "MANUAL_TEXT") { + t.Errorf("cursor should inline the manual variant") + } + // Codex's SKILL.md renders the manual variant. + cod := read("codex", "agent", ".agents", "skills", "c", "SKILL.md") + if !strings.Contains(cod, "MANUAL_TEXT") || strings.Contains(cod, "PIPELINE_TEXT") { + t.Errorf("codex SKILL.md should render the manual variant") + } + // Neutral's SKILL.md keeps the default (pipeline-preferred) and fans out. + neu := read("neutral", "agent", "skills", "c", "SKILL.md") + if !strings.Contains(neu, "PIPELINE_TEXT") { + t.Errorf("neutral SKILL.md should render the default (pipeline) variant") + } + if !strings.Contains(read("neutral", "agent", "skills", "c", "SKILL.manual.md"), "MANUAL_TEXT") { + t.Errorf("neutral should still fan out SKILL.manual.md") + } +} diff --git a/go/internal/compile/variants_test.go b/go/internal/compile/variants_test.go new file mode 100644 index 0000000..b083a8f --- /dev/null +++ b/go/internal/compile/variants_test.go @@ -0,0 +1,30 @@ +package compile + +import ( + "strings" + "testing" + + "github.com/buchk/TypeFerence/go/internal/jsonx" + "github.com/buchk/TypeFerence/go/internal/resolve" +) + +func TestSkillValueOmitsVariantsWhenUnimodal(t *testing.T) { + out := jsonx.Indented(skillValue(resolve.ResolvedSkill{DispatchName: "a.b", Instructions: "x"})) + if strings.Contains(out, "variants") { + t.Errorf("a unimodal skill must not emit a variants member:\n%s", out) + } +} + +func TestSkillValueEmitsVariantsWhenMultimodal(t *testing.T) { + out := jsonx.Indented(skillValue(resolve.ResolvedSkill{ + DispatchName: "a.b", + Instructions: "strict", + Variants: map[string]string{"pipeline": "strict", "manual": "explain"}, + })) + if !strings.Contains(out, `"variants"`) { + t.Errorf("a multimodal skill must emit a variants member:\n%s", out) + } + if !strings.Contains(out, "explain") { + t.Errorf("variants member must contain each mode's instructions:\n%s", out) + } +} diff --git a/go/internal/lsp/features.go b/go/internal/lsp/features.go new file mode 100644 index 0000000..9d5c653 --- /dev/null +++ b/go/internal/lsp/features.go @@ -0,0 +1,83 @@ +package lsp + +import ( + "regexp" + "strings" +) + +// topLevelFields are the resource fields offered as completions at column 0. +var topLevelFields = []string{ + "schemaVersion", "kind", "id", "displayName", "description", "binds", + "emit", "embeds", "requiresSlots", "requiresCapabilities", "slots", + "workingNorms", "contextFiles", "context", "skills", "instructions", + "inputSchema", "outputSchema", "contextType", "schema", + "requiresContextTypes", "requiresTools", "visibility", "variants", +} + +// kinds are the resource kinds offered after `kind:`. +var kinds = []string{"agent", "profile", "interface", "capability", "skill", "context", "contextType", "tool"} + +// idToken matches a resource identifier (namespace/name@semver). +var idToken = regexp.MustCompile(`[a-z0-9][a-z0-9.-]*(?:/[a-z0-9][a-z0-9.-]*)+@[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?`) + +// completions returns completion labels for the cursor: kind values after +// `kind:`, otherwise the top-level field names when the cursor is in a line's +// leading token (no colon yet). +func completions(text string, line, char int) []string { + cur := lineAt(text, line) + prefix := cur + if char >= 0 && char <= len(cur) { + prefix = cur[:char] + } + if strings.HasPrefix(strings.TrimSpace(prefix), "kind:") { + return kinds + } + if !strings.Contains(prefix, ":") { + return topLevelFields + } + return nil +} + +func lineAt(text string, line int) string { + lines := strings.Split(text, "\n") + if line < 0 || line >= len(lines) { + return "" + } + return strings.TrimRight(lines[line], "\r") +} + +// tokenAt returns the resource-id token spanning the cursor, or "". +func tokenAt(text string, line, char int) string { + cur := lineAt(text, line) + for _, m := range idToken.FindAllStringIndex(cur, -1) { + if char >= m[0] && char <= m[1] { + return cur[m[0]:m[1]] + } + } + return "" +} + +// symbolOf extracts (id, kind) from a resource's text for a document symbol. +func symbolOf(text string) (id, kind string) { + for _, raw := range strings.Split(text, "\n") { + if v, ok := scalarField(raw, "id"); ok { + id = v + } + if v, ok := scalarField(raw, "kind"); ok { + kind = v + } + } + return id, kind +} + +func scalarField(line, name string) (string, bool) { + t := strings.TrimSpace(strings.TrimRight(line, "\r")) + if strings.HasPrefix(t, name+":") { + return strings.TrimSpace(strings.TrimPrefix(t, name+":")), true + } + return "", false +} + +func isSource(path string) bool { + return strings.HasSuffix(path, ".tfer") || strings.HasSuffix(path, ".yaml") +} diff --git a/go/internal/lsp/features_test.go b/go/internal/lsp/features_test.go new file mode 100644 index 0000000..3d2f9e1 --- /dev/null +++ b/go/internal/lsp/features_test.go @@ -0,0 +1,136 @@ +package lsp + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestCompletionsKindAndFields(t *testing.T) { + if got := completions("kind: ", 0, 6); len(got) == 0 || got[0] != "agent" { + t.Errorf("expected kind completions after `kind:`, got %v", got) + } + fields := completions("sch", 0, 3) + if !contains(fields, "schemaVersion") { + t.Errorf("expected field completions to include schemaVersion, got %v", fields) + } + if got := completions("id: foo", 0, 7); got != nil { + t.Errorf("expected no completions in a value position, got %v", got) + } +} + +func TestTokenAtAndSymbolOf(t *testing.T) { + text := "kind: skill\nid: acme/skills/s@1.0.0\nbinds: acme/capabilities/c@1.0.0\n" + if tok := tokenAt(text, 2, 15); tok != "acme/capabilities/c@1.0.0" { + t.Errorf("tokenAt on the binds id: got %q", tok) + } + id, kind := symbolOf(text) + if id != "acme/skills/s@1.0.0" || kind != "skill" { + t.Errorf("symbolOf: got (%q, %q)", id, kind) + } +} + +func writeFile(t *testing.T, root, name, content string) string { + t.Helper() + full := filepath.Join(root, name) + if err := os.WriteFile(full, []byte(content), 0o644); err != nil { + t.Fatal(err) + } + return full +} + +func runSession(t *testing.T, rootURI string, msgs ...string) []map[string]any { + t.Helper() + input := frame("initialize", 1, map[string]any{"rootUri": rootURI}) + for _, m := range msgs { + input += m + } + input += frame("exit", nil, nil) + var out bytes.Buffer + if err := NewServer("test").Run(strings.NewReader(input), &out); err != nil { + t.Fatal(err) + } + return readFrames(t, out.String()) +} + +func resultFor(frames []map[string]any, id int) any { + for _, m := range frames { + if n, ok := m["id"].(float64); ok && int(n) == id { + return m["result"] + } + } + return nil +} + +func TestDefinitionResolvesResourceID(t *testing.T) { + root := t.TempDir() + writeFile(t, root, "cap.yaml", "schemaVersion: 3\nkind: capability\nid: acme/cap/c@1.0.0\n") + skillText := "schemaVersion: 3\nkind: skill\nid: acme/skills/s@1.0.0\nbinds: acme/cap/c@1.0.0\n" + skillURI := pathToURI(writeFile(t, root, "skill.yaml", skillText)) + frames := runSession(t, pathToURI(root), + frame("textDocument/didOpen", nil, docParams(skillURI, skillText)), + frame("textDocument/definition", 2, map[string]any{ + "textDocument": map[string]any{"uri": skillURI}, + "position": map[string]any{"line": 3, "character": 12}, // on the binds id + }), + ) + res, _ := resultFor(frames, 2).(map[string]any) + uri, _ := res["uri"].(string) + if !strings.Contains(uri, "cap.yaml") { + t.Errorf("definition should resolve the binds id to cap.yaml, got %q", uri) + } +} + +func TestDocumentSymbolReturnsResource(t *testing.T) { + root := t.TempDir() + text := "schemaVersion: 3\nkind: capability\nid: acme/cap/c@1.0.0\n" + uri := pathToURI(writeFile(t, root, "cap.yaml", text)) + frames := runSession(t, pathToURI(root), + frame("textDocument/didOpen", nil, docParams(uri, text)), + frame("textDocument/documentSymbol", 2, map[string]any{ + "textDocument": map[string]any{"uri": uri}, + }), + ) + syms, _ := resultFor(frames, 2).([]any) + if len(syms) != 1 { + t.Fatalf("expected one document symbol, got %v", syms) + } + name, _ := syms[0].(map[string]any)["name"].(string) + if !strings.Contains(name, "acme/cap/c@1.0.0") { + t.Errorf("symbol name should include the id, got %q", name) + } +} + +func TestCompositionDiagnosticSurfaces(t *testing.T) { + root := t.TempDir() + // skill binds a capability that does not exist -> workspace does not compose + agentText := "schemaVersion: 3\nkind: agent\nid: acme/agent@1.0.0\nskills:\n - ref: acme/skills/s@1.0.0\n" + agentURI := pathToURI(writeFile(t, root, "agent.yaml", agentText)) + writeFile(t, root, "skill.yaml", "schemaVersion: 3\nkind: skill\nid: acme/skills/s@1.0.0\nbinds: acme/cap/missing@1.0.0\n") + frames := runSession(t, pathToURI(root), + frame("textDocument/didOpen", nil, docParams(agentURI, agentText)), + ) + found := false + for _, m := range frames { + if m["method"] == "textDocument/publishDiagnostics" { + p := m["params"].(map[string]any) + if p["uri"] == agentURI && len(p["diagnostics"].([]any)) >= 1 { + found = true + } + } + } + if !found { + t.Error("expected a composition diagnostic for a workspace that does not resolve") + } +} + +func contains(s []string, v string) bool { + for _, x := range s { + if x == v { + return true + } + } + return false +} diff --git a/go/internal/lsp/server.go b/go/internal/lsp/server.go new file mode 100644 index 0000000..a124f83 --- /dev/null +++ b/go/internal/lsp/server.go @@ -0,0 +1,491 @@ +// Package lsp implements a minimal Language Server Protocol server for +// TypeFerence sources (`.tfer` and `.yaml`). It provides authoring diagnostics +// by running the loader's single-document shape validation on each open buffer. +// +// Scope: this is the v0 surface. It reports per-file syntax and shape errors — +// malformed frontmatter fences, unknown fields, bad kinds, skills that do not +// bind a capability, context objects without a contextType, and so on. It does +// not yet resolve across a source tree, so composition diagnostics (embedding +// ambiguity, unsatisfied interfaces, unresolved references) are a planned +// follow-up that requires whole-workspace resolution and source-root discovery. +package lsp + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "io/fs" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + + "github.com/buchk/TypeFerence/go/internal/compile" + "github.com/buchk/TypeFerence/go/internal/resource" +) + +// Server is a single-connection LSP server speaking JSON-RPC 2.0 over a stream. +type Server struct { + version string + r *bufio.Reader + w io.Writer + wmu sync.Mutex + docs map[string]string // uri -> latest buffer text + roots []string // workspace root paths + index map[string]string // resource id -> defining file uri +} + +// NewServer returns a server that reports the given version to clients. +func NewServer(version string) *Server { + return &Server{version: version, docs: map[string]string{}, index: map[string]string{}} +} + +// message is a JSON-RPC 2.0 request, response, or notification. +type message struct { + JSONRPC string `json:"jsonrpc"` + ID json.RawMessage `json:"id,omitempty"` + Method string `json:"method,omitempty"` + Params json.RawMessage `json:"params,omitempty"` + Result json.RawMessage `json:"result,omitempty"` + Error *rpcError `json:"error,omitempty"` +} + +type rpcError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +// Run serves requests from in and writes responses to out until the client +// sends `exit` or the stream closes. +func (s *Server) Run(in io.Reader, out io.Writer) error { + s.r = bufio.NewReader(in) + s.w = out + for { + msg, err := s.read() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + if s.dispatch(msg) { + return nil + } + } +} + +// read parses one Content-Length-framed JSON-RPC message. +func (s *Server) read() (*message, error) { + var length int + for { + line, err := s.r.ReadString('\n') + if err != nil { + return nil, err + } + line = strings.TrimRight(line, "\r\n") + if line == "" { + break + } + if strings.HasPrefix(strings.ToLower(line), "content-length:") { + fmt.Sscanf(strings.TrimSpace(line[len("content-length:"):]), "%d", &length) + } + } + if length == 0 { + return &message{}, nil + } + buf := make([]byte, length) + if _, err := io.ReadFull(s.r, buf); err != nil { + return nil, err + } + var msg message + if err := json.Unmarshal(buf, &msg); err != nil { + return nil, err + } + return &msg, nil +} + +func (s *Server) write(msg *message) { + msg.JSONRPC = "2.0" + body, err := json.Marshal(msg) + if err != nil { + return + } + s.wmu.Lock() + defer s.wmu.Unlock() + fmt.Fprintf(s.w, "Content-Length: %d\r\n\r\n", len(body)) + s.w.Write(body) +} + +// dispatch handles one message and reports whether the server should exit. +func (s *Server) dispatch(msg *message) bool { + switch msg.Method { + case "initialize": + s.handleInitialize(msg.Params) + s.reply(msg.ID, s.initializeResult()) + case "initialized": + // no-op + case "textDocument/didOpen": + s.handleOpen(msg.Params) + case "textDocument/didChange": + s.handleChange(msg.Params) + case "textDocument/didSave": + s.handleSave(msg.Params) + case "textDocument/didClose": + s.handleClose(msg.Params) + case "textDocument/completion": + s.reply(msg.ID, s.handleCompletion(msg.Params)) + case "textDocument/definition": + s.reply(msg.ID, s.handleDefinition(msg.Params)) + case "textDocument/documentSymbol": + s.reply(msg.ID, s.handleDocumentSymbol(msg.Params)) + case "shutdown": + s.reply(msg.ID, nil) + case "exit": + return true + default: + if len(msg.ID) > 0 { + s.write(&message{ID: msg.ID, Error: &rpcError{Code: -32601, Message: "method not found: " + msg.Method}}) + } + } + return false +} + +// handleInitialize captures workspace roots and indexes resource ids. +func (s *Server) handleInitialize(raw json.RawMessage) { + var p struct { + RootURI string `json:"rootUri"` + WorkspaceFolders []struct { + URI string `json:"uri"` + } `json:"workspaceFolders"` + } + json.Unmarshal(raw, &p) + seen := map[string]bool{} + add := func(uri string) { + if uri == "" { + return + } + path := uriToPath(uri) + if !seen[path] { + seen[path] = true + s.roots = append(s.roots, path) + } + } + add(p.RootURI) + for _, f := range p.WorkspaceFolders { + add(f.URI) + } + s.buildIndex() +} + +// buildIndex maps every resource id under the workspace roots to its file uri. +func (s *Server) buildIndex() { + index := map[string]string{} + for _, root := range s.roots { + filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() || !isSource(path) { + return nil + } + raw, readErr := os.ReadFile(path) + if readErr != nil { + return nil + } + if id, _ := symbolOf(string(raw)); id != "" { + index[id] = pathToURI(path) + } + return nil + }) + } + s.index = index +} + +func (s *Server) reply(id json.RawMessage, result any) { + if len(id) == 0 { + return + } + raw := json.RawMessage("null") + if result != nil { + b, err := json.Marshal(result) + if err != nil { + return + } + raw = b + } + s.write(&message{ID: id, Result: raw}) +} + +func (s *Server) initializeResult() any { + return map[string]any{ + "capabilities": map[string]any{ + // 1 = full document sync: each change carries the whole buffer. + "textDocumentSync": 1, + "completionProvider": map[string]any{}, + "definitionProvider": true, + "documentSymbolProvider": true, + }, + "serverInfo": map[string]any{ + "name": "typeference-lsp", + "version": s.version, + }, + } +} + +type textDocumentItem struct { + URI string `json:"uri"` + Text string `json:"text"` +} + +func (s *Server) handleOpen(raw json.RawMessage) { + var p struct { + TextDocument textDocumentItem `json:"textDocument"` + } + if json.Unmarshal(raw, &p) != nil { + return + } + s.docs[p.TextDocument.URI] = p.TextDocument.Text + s.publish(p.TextDocument.URI, p.TextDocument.Text, true) +} + +func (s *Server) handleChange(raw json.RawMessage) { + var p struct { + TextDocument struct { + URI string `json:"uri"` + } `json:"textDocument"` + ContentChanges []struct { + Text string `json:"text"` + } `json:"contentChanges"` + } + if json.Unmarshal(raw, &p) != nil || len(p.ContentChanges) == 0 { + return + } + text := p.ContentChanges[len(p.ContentChanges)-1].Text + s.docs[p.TextDocument.URI] = text + // Keystroke: fast single-file shape diagnostics only, no whole-workspace + // resolution (it reads from disk and would be noisy mid-edit). + s.publish(p.TextDocument.URI, text, false) +} + +func (s *Server) handleSave(raw json.RawMessage) { + uri := uriParam(raw) + s.buildIndex() // disk changed; refresh id -> uri + if text, ok := s.docs[uri]; ok { + s.publish(uri, text, true) + } +} + +func (s *Server) handleClose(raw json.RawMessage) { + uri := uriParam(raw) + delete(s.docs, uri) + s.publishDiagnostics(uri, nil) +} + +func uriParam(raw json.RawMessage) string { + var p struct { + TextDocument struct { + URI string `json:"uri"` + } `json:"textDocument"` + } + json.Unmarshal(raw, &p) + return p.TextDocument.URI +} + +// publish validates one buffer and pushes its diagnostics. When withComposition +// is set it also reports whole-workspace resolution errors that reference this +// file (composition diagnostics read from disk, so they run on open/save). +func (s *Server) publish(uri, text string, withComposition bool) { + path := uriToPath(uri) + if !isSource(path) { + return + } + var diags []diagnostic + if err := resource.CheckDocument(path, text); err != nil { + diags = append(diags, diagnostic{ + Range: errorRange(text), + Severity: 1, // Error + Source: "typeference", + Message: stripFilePrefix(err.Error(), filepath.Base(path)), + }) + } else if withComposition { + for _, m := range s.compositionErrorsFor(text, path) { + diags = append(diags, diagnostic{ + Range: errorRange(text), + Severity: 1, + Source: "typeference", + Message: m, + }) + } + } + s.publishDiagnostics(uri, diags) +} + +// compositionErrorsFor resolves each workspace root from disk and returns the +// resolution errors to show on this file: those that name this file (by id or +// basename), plus any that cannot be attributed to a different indexed +// resource (so an unlocated workspace error still surfaces somewhere). +func (s *Server) compositionErrorsFor(text, path string) []string { + id, _ := symbolOf(text) + base := filepath.Base(path) + seen := map[string]bool{} + out := []string{} + for _, root := range s.roots { + _, err := compile.Validate(root, "") + if err == nil { + continue + } + m := err.Error() + if seen[m] { + continue + } + seen[m] = true + if strings.Contains(m, base) || (id != "" && strings.Contains(m, id)) || !s.attributableElsewhere(m, id) { + out = append(out, m) + } + } + return out +} + +// attributableElsewhere reports whether a message names an indexed resource +// other than selfID (so it belongs on that resource's file, not this one). +func (s *Server) attributableElsewhere(msg, selfID string) bool { + for otherID := range s.index { + if otherID != selfID && strings.Contains(msg, otherID) { + return true + } + } + return false +} + +// handleCompletion offers kind values and top-level field names. +func (s *Server) handleCompletion(raw json.RawMessage) any { + uri, line, char := positionParams(raw) + items := []map[string]any{} + for _, label := range completions(s.docs[uri], line, char) { + items = append(items, map[string]any{"label": label}) + } + return map[string]any{"isIncomplete": false, "items": items} +} + +// handleDefinition jumps from a resource-id token to its defining file. +func (s *Server) handleDefinition(raw json.RawMessage) any { + uri, line, char := positionParams(raw) + id := tokenAt(s.docs[uri], line, char) + target, ok := s.index[id] + if id == "" || !ok { + return nil + } + return map[string]any{ + "uri": target, + "range": rng{Start: position{0, 0}, End: position{0, 0}}, + } +} + +// handleDocumentSymbol returns the resource as a single symbol (id + kind). +func (s *Server) handleDocumentSymbol(raw json.RawMessage) any { + uri := uriParam(raw) + id, kind := symbolOf(s.docs[uri]) + if id == "" { + return []any{} + } + name := id + if kind != "" { + name = kind + " " + id + } + // SymbolKind 5 = Class; a resource is the closest analogue. + return []map[string]any{{ + "name": name, + "kind": 5, + "range": rng{Start: position{0, 0}, End: position{0, 0}}, + "selectionRange": rng{Start: position{0, 0}, End: position{0, 0}}, + }} +} + +func positionParams(raw json.RawMessage) (uri string, line, char int) { + var p struct { + TextDocument struct { + URI string `json:"uri"` + } `json:"textDocument"` + Position struct { + Line int `json:"line"` + Character int `json:"character"` + } `json:"position"` + } + json.Unmarshal(raw, &p) + return p.TextDocument.URI, p.Position.Line, p.Position.Character +} + +func (s *Server) publishDiagnostics(uri string, diags []diagnostic) { + if diags == nil { + diags = []diagnostic{} + } + params, err := json.Marshal(map[string]any{ + "uri": uri, + "diagnostics": diags, + }) + if err != nil { + return + } + s.write(&message{Method: "textDocument/publishDiagnostics", Params: params}) +} + +type position struct { + Line int `json:"line"` + Character int `json:"character"` +} + +type rng struct { + Start position `json:"start"` + End position `json:"end"` +} + +type diagnostic struct { + Range rng `json:"range"` + Severity int `json:"severity"` + Source string `json:"source"` + Message string `json:"message"` +} + +// errorRange anchors a whole-document diagnostic to the first line. The loader +// reports one shape error per document without a position, so v0 highlights the +// opening line rather than guessing an offset. +func errorRange(text string) rng { + end := 1 + if nl := strings.IndexByte(text, '\n'); nl > 0 { + end = nl + } + return rng{Start: position{0, 0}, End: position{0, end}} +} + +// stripFilePrefix removes a leading "basename: " that loader errors carry, since +// the client already associates the diagnostic with the file by URI. +func stripFilePrefix(msg, base string) string { + return strings.TrimPrefix(msg, base+": ") +} + +// pathToURI converts a local filesystem path to a file:// URI, adding the +// leading slash before a Windows drive letter (C:\x -> file:///C:/x). +func pathToURI(path string) string { + p := filepath.ToSlash(path) + if runtime.GOOS == "windows" && len(p) >= 2 && p[1] == ':' { + p = "/" + p + } + // url.URL.String() percent-encodes the path, so spaces and other reserved + // characters produce a valid file URI. + u := url.URL{Scheme: "file", Path: p} + return u.String() +} + +// uriToPath converts a file:// URI to a local filesystem path, handling the +// leading-slash Windows drive form (file:///C:/x -> C:\x). +func uriToPath(uri string) string { + u, err := url.Parse(uri) + if err != nil || u.Scheme != "file" { + return uri + } + p := u.Path + if runtime.GOOS == "windows" && len(p) >= 3 && p[0] == '/' && p[2] == ':' { + p = p[1:] + } + return filepath.FromSlash(p) +} diff --git a/go/internal/lsp/server_test.go b/go/internal/lsp/server_test.go new file mode 100644 index 0000000..ff30f00 --- /dev/null +++ b/go/internal/lsp/server_test.go @@ -0,0 +1,128 @@ +package lsp + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + "testing" +) + +func frame(method string, id any, params any) string { + m := map[string]any{"jsonrpc": "2.0", "method": method} + if id != nil { + m["id"] = id + } + if params != nil { + m["params"] = params + } + b, _ := json.Marshal(m) + return fmt.Sprintf("Content-Length: %d\r\n\r\n%s", len(b), b) +} + +func readFrames(t *testing.T, out string) []map[string]any { + t.Helper() + var msgs []map[string]any + rest := out + for { + i := strings.Index(rest, "Content-Length: ") + if i < 0 { + break + } + rest = rest[i+len("Content-Length: "):] + j := strings.Index(rest, "\r\n\r\n") + if j < 0 { + break + } + var n int + fmt.Sscanf(rest[:j], "%d", &n) + body := rest[j+4 : j+4+n] + var m map[string]any + if err := json.Unmarshal([]byte(body), &m); err != nil { + t.Fatalf("bad frame: %v", err) + } + msgs = append(msgs, m) + rest = rest[j+4+n:] + } + return msgs +} + +func docParams(uri, text string) map[string]any { + return map[string]any{"textDocument": map[string]any{"uri": uri, "text": text}} +} + +func TestServerDiagnostics(t *testing.T) { + goodSkill := "---\nschemaVersion: 3\nkind: skill\nid: t/skills/s@1.0.0\nbinds: t/capabilities/c@1.0.0\n---\ndo the thing\n" + badSkill := "---\nschemaVersion: 3\nkind: skill\nid: t/skills/s@1.0.0\n---\ndo the thing\n" // missing binds + + input := frame("initialize", 1, map[string]any{}) + + frame("textDocument/didOpen", nil, docParams("file:///tmp/bad.tfer", badSkill)) + + frame("textDocument/didOpen", nil, docParams("file:///tmp/good.tfer", goodSkill)) + + frame("shutdown", 2, nil) + + frame("exit", nil, nil) + + var out bytes.Buffer + if err := NewServer("test").Run(strings.NewReader(input), &out); err != nil { + t.Fatal(err) + } + frames := readFrames(t, out.String()) + + sawInit := false + badDiags, goodDiags := -1, -1 + for _, m := range frames { + if m["method"] == "textDocument/publishDiagnostics" { + p := m["params"].(map[string]any) + uri := p["uri"].(string) + diags := p["diagnostics"].([]any) + switch { + case strings.Contains(uri, "bad.tfer"): + badDiags = len(diags) + case strings.Contains(uri, "good.tfer"): + goodDiags = len(diags) + } + } + if r, ok := m["result"].(map[string]any); ok { + if _, has := r["capabilities"]; has { + sawInit = true + } + } + } + if !sawInit { + t.Error("expected an initialize result advertising capabilities") + } + if badDiags != 1 { + t.Errorf("bad.tfer: want 1 diagnostic, got %d", badDiags) + } + if goodDiags != 0 { + t.Errorf("good.tfer: want 0 diagnostics, got %d", goodDiags) + } +} + +func TestServerBadFrontmatterFenceDiagnostic(t *testing.T) { + // A .tfer with no closing fence must produce exactly one diagnostic. + broken := "---\nschemaVersion: 3\nkind: skill\nid: t/skills/s@1.0.0\nbinds: t/capabilities/c@1.0.0\n" + input := frame("initialize", 1, map[string]any{}) + + frame("textDocument/didOpen", nil, docParams("file:///tmp/broken.tfer", broken)) + + frame("exit", nil, nil) + + var out bytes.Buffer + if err := NewServer("test").Run(strings.NewReader(input), &out); err != nil { + t.Fatal(err) + } + found := false + for _, m := range readFrames(t, out.String()) { + if m["method"] == "textDocument/publishDiagnostics" { + p := m["params"].(map[string]any) + diags := p["diagnostics"].([]any) + if len(diags) == 1 { + msg := diags[0].(map[string]any)["message"].(string) + if strings.Contains(msg, "closing '---' frontmatter fence") { + found = true + } + } + } + } + if !found { + t.Error("expected a closing-fence diagnostic for the broken .tfer") + } +} diff --git a/go/internal/resolve/exposure_test.go b/go/internal/resolve/exposure_test.go new file mode 100644 index 0000000..adcd917 --- /dev/null +++ b/go/internal/resolve/exposure_test.go @@ -0,0 +1,52 @@ +package resolve + +import ( + "testing" + + "github.com/buchk/TypeFerence/go/internal/resource" +) + +func TestCapabilityInternalByDefault(t *testing.T) { + set := baseSkillAgent(nil, nil) // capability t/cap/c@1.0.0 has no visibility + agent, err := New(set).Resolve("t/agent@1.0.0") + if err != nil { + t.Fatal(err) + } + if len(agent.ExposedSkills()) != 0 { + t.Errorf("capabilities are internal by default; expected no exposed skills") + } + if agent.Skills[0].Exposed { + t.Errorf("skill should not be exposed by default") + } +} + +func TestExposedCapabilitySurfaces(t *testing.T) { + set := baseSkillAgent(nil, nil) + set["t/cap/c@1.0.0"].Visibility = "exposed" + agent, err := New(set).Resolve("t/agent@1.0.0") + if err != nil { + t.Fatal(err) + } + exposed := agent.ExposedSkills() + if len(exposed) != 1 || exposed[0].CapabilityID != "t/cap/c@1.0.0" { + t.Fatalf("expected the exposed capability on the public surface, got %+v", exposed) + } +} + +func TestExposurePromotesThroughEmbedding(t *testing.T) { + cap := doc("capability", "t/cap/c@1.0.0", func(d *resource.Document) { d.Visibility = "exposed" }) + skill := doc("skill", "t/skills/s@1.0.0", func(d *resource.Document) { d.Binds = "t/cap/c@1.0.0" }) + profile := doc("profile", "t/profile@1.0.0", func(d *resource.Document) { + d.Skills = []resource.SkillBinding{{Ref: "t/skills/s@1.0.0"}} + }) + agent := doc("agent", "t/agent@1.0.0", func(d *resource.Document) { + d.Embeds = []string{"t/profile@1.0.0"} + }) + resolved, err := New(docSet(cap, skill, profile, agent)).Resolve("t/agent@1.0.0") + if err != nil { + t.Fatal(err) + } + if len(resolved.ExposedSkills()) != 1 { + t.Errorf("exposure should promote through embedding onto the agent's surface") + } +} diff --git a/go/internal/resolve/gating_test.go b/go/internal/resolve/gating_test.go new file mode 100644 index 0000000..b5043da --- /dev/null +++ b/go/internal/resolve/gating_test.go @@ -0,0 +1,85 @@ +package resolve + +import ( + "strings" + "testing" + + "github.com/buchk/TypeFerence/go/internal/resource" +) + +func TestAllowedContextTypesPermitsRefinement(t *testing.T) { + set := baseSkillAgent(nil, func(a *resource.Document) { + a.Context = []string{"t/notes/n@1.0.0"} + a.AllowedContextTypes = []string{"t/ct/cast@1.0.0"} + }) + set["t/ct/cast@1.0.0"] = doc("contextType", "t/ct/cast@1.0.0", nil) + set["t/ct/gov@1.0.0"] = doc("contextType", "t/ct/gov@1.0.0", func(d *resource.Document) { d.Embeds = []string{"t/ct/cast@1.0.0"} }) + set["t/notes/n@1.0.0"] = doc("context", "t/notes/n@1.0.0", func(d *resource.Document) { d.ContextType = "t/ct/gov@1.0.0" }) + if _, err := New(set).Resolve("t/agent@1.0.0"); err != nil { + t.Fatalf("a governed cast should satisfy an allow-list of [cast]: %v", err) + } +} + +func TestAllowedContextTypesRejectsOutsider(t *testing.T) { + set := baseSkillAgent(nil, func(a *resource.Document) { + a.Context = []string{"t/notes/n@1.0.0"} + a.AllowedContextTypes = []string{"t/ct/other@1.0.0"} + }) + set["t/ct/other@1.0.0"] = doc("contextType", "t/ct/other@1.0.0", nil) + set["t/ct/cast@1.0.0"] = doc("contextType", "t/ct/cast@1.0.0", nil) + set["t/notes/n@1.0.0"] = doc("context", "t/notes/n@1.0.0", func(d *resource.Document) { d.ContextType = "t/ct/cast@1.0.0" }) + _, err := New(set).Resolve("t/agent@1.0.0") + if err == nil || !strings.Contains(err.Error(), "not among the allowed") { + t.Fatalf("expected allow-list rejection, got %v", err) + } +} + +func TestAllowListIntersectsThroughEmbeds(t *testing.T) { + cap := doc("capability", "t/cap/c@1.0.0", nil) + skill := doc("skill", "t/skills/s@1.0.0", func(d *resource.Document) { d.Binds = "t/cap/c@1.0.0" }) + profile := doc("profile", "t/profile@1.0.0", func(d *resource.Document) { + d.Skills = []resource.SkillBinding{{Ref: "t/skills/s@1.0.0"}} + d.AllowedContextTypes = []string{"t/ct/a@1.0.0", "t/ct/b@1.0.0"} + }) + a := doc("contextType", "t/ct/a@1.0.0", nil) + b := doc("contextType", "t/ct/b@1.0.0", nil) + c := doc("contextType", "t/ct/c@1.0.0", nil) + objA := doc("context", "t/notes/a@1.0.0", func(d *resource.Document) { d.ContextType = "t/ct/a@1.0.0" }) + agent := doc("agent", "t/agent@1.0.0", func(d *resource.Document) { + d.Embeds = []string{"t/profile@1.0.0"} + d.AllowedContextTypes = []string{"t/ct/b@1.0.0", "t/ct/c@1.0.0"} // effective = [b] + d.Context = []string{"t/notes/a@1.0.0"} // type a, now excluded + }) + _, err := New(docSet(cap, skill, profile, a, b, c, objA, agent)).Resolve("t/agent@1.0.0") + if err == nil || !strings.Contains(err.Error(), "not among the allowed") { + t.Fatalf("intersection of [a,b] and [b,c] should exclude type a, got %v", err) + } +} + +func TestVariantContextRequirementAggregatedAndChecked(t *testing.T) { + set := baseSkillAgent(func(s *resource.Document) { + s.Variants = map[string]resource.Variant{ + "manual": {Instructions: "m"}, + "a2a": {Instructions: "a", RequiresContextTypes: []string{"t/ct/gov@1.0.0"}}, + } + }, nil) // agent holds no context + set["t/ct/gov@1.0.0"] = doc("contextType", "t/ct/gov@1.0.0", nil) + _, err := New(set).Resolve("t/agent@1.0.0") + if err == nil || !strings.Contains(err.Error(), "requires context type") { + t.Fatalf("a variant's context-type requirement must be enforced at the agent, got %v", err) + } +} + +func TestVariantToolRequirementAggregated(t *testing.T) { + set := baseSkillAgent(func(s *resource.Document) { + s.Variants = map[string]resource.Variant{ + "pipeline": {Instructions: "p"}, + "a2a": {Instructions: "a", RequiresTools: []string{"t/tools/reader@1.0.0"}}, + } + }, nil) + // tool not declared -> error + _, err := New(set).Resolve("t/agent@1.0.0") + if err == nil || !strings.Contains(err.Error(), "requires tool") { + t.Fatalf("a variant's tool requirement must be enforced, got %v", err) + } +} diff --git a/go/internal/resolve/resolver.go b/go/internal/resolve/resolver.go index 93165f0..de8866a 100644 --- a/go/internal/resolve/resolver.go +++ b/go/internal/resolve/resolver.go @@ -5,6 +5,7 @@ package resolve import ( + "encoding/json" "sort" "strings" @@ -20,31 +21,58 @@ type ProvenanceEntry struct { // ResolvedSkill is a concrete skill after promotion and contract checks. type ResolvedSkill struct { - DispatchName string - CapabilityID string - ImplementationID string - Description string - Instructions string - InputSchema string - OutputSchema string - ContextFiles []string - Provenance []ProvenanceEntry + DispatchName string + CapabilityID string + ImplementationID string + Description string + Instructions string + InputSchema string + OutputSchema string + ContextFiles []string + RequiresContextTypes []string + RequiresTools []string + // Exposed is true when the bound capability's visibility is "exposed": + // part of the agent's public callable surface, eligible for a callable + // card (ADR-0015). Rides the skill, so promotion carries it automatically. + Exposed bool + // Sealed marks a binding an embedder may not override or rebind; Required + // marks it mandatory (ADR-0016). Both ride the skill through promotion. + Sealed bool + Required bool + // Variants maps mode name to that mode's instructions for a multimodal + // skill (ADR-0012); nil for a unimodal skill. Instructions above holds the + // default (neutral) variant's rendering. + Variants map[string]string + Provenance []ProvenanceEntry } // ResolvedAgent is a fully composed agent or profile. type ResolvedAgent struct { - ID string - DisplayName string - Description string - Emit bool - Embeds []string - Satisfies []string - Slots map[string]string - SlotKeys []string // canonical order for Slots - WorkingNorms []string - ContextFiles []string - Skills []ResolvedSkill - Provenance []ProvenanceEntry + ID string + DisplayName string + Description string + Emit bool + Embeds []string + Satisfies []string + Slots map[string]string + SlotKeys []string // canonical order for Slots + WorkingNorms []string + ContextFiles []string + Context []string + ContextObjects []ResolvedContextRef + AllowedContextTypes []string + Skills []ResolvedSkill + Provenance []ProvenanceEntry +} + +// ResolvedContextRef is a context object an agent holds by id, with the +// contextType it instantiates and its materialized content (ADR-0013). Content +// lets a target inline the held context, not merely reference it. +type ResolvedContextRef struct { + ID string + DisplayName string + ContextType string + Content string } type interfaceContract struct { @@ -85,6 +113,25 @@ func (r *Resolver) ResolveAll() ([]*ResolvedAgent, error) { return nil, err } } + for _, id := range r.idsOfKind("contextType") { + if _, err := r.contextTypeClosure(id, map[string]bool{}); err != nil { + return nil, err + } + } + for _, id := range r.idsOfKind("context") { + obj := r.resources[id] + if _, err := r.contextTypeClosure(obj.ContextType, map[string]bool{}); err != nil { + return nil, resource.Errorf("%s: %s", id, err) + } + if err := r.validateContextFields(obj); err != nil { + return nil, err + } + } + for _, id := range r.idsOfKind("tool") { + if err := r.validateTool(r.resources[id]); err != nil { + return nil, err + } + } for _, id := range r.idsOfKind("profile") { if _, err := r.resolveComponent(id, map[string]bool{}, false); err != nil { return nil, err @@ -166,10 +213,20 @@ func (r *Resolver) resolveComponent(id string, visiting map[string]bool, require } norms := distinct(concatNorms(embedded, current)) contexts := distinct(normalizeAll(concatContexts(embedded, current))) + contextRefs := distinct(concatContextRefs(embedded, current)) + allowedContextTypes := intersectAllowLists(embedded, current) skills, skillDepths, err := r.mergeSkills(id, current, embedded, contexts) if err != nil { return nil, err } + if current.Kind == "agent" { + if err := r.checkSkillDependencies(id, skills, contextRefs); err != nil { + return nil, err + } + if err := r.checkAllowedContext(id, contextRefs, allowedContextTypes); err != nil { + return nil, err + } + } satisfies := []string{} for _, interfaceID := range r.idsOfKind("interface") { @@ -228,18 +285,21 @@ func (r *Resolver) resolveComponent(id string, visiting map[string]bool, require } resolved := &ResolvedAgent{ - ID: id, - DisplayName: displayName, - Description: current.Description, - Emit: current.Emit, - Embeds: append([]string{}, current.Embeds...), - Satisfies: satisfies, - Slots: slots, - SlotKeys: slotKeys, - WorkingNorms: norms, - ContextFiles: contexts, - Skills: sortedSkills, - Provenance: provenance, + ID: id, + DisplayName: displayName, + Description: current.Description, + Emit: current.Emit, + Embeds: append([]string{}, current.Embeds...), + Satisfies: satisfies, + Slots: slots, + SlotKeys: slotKeys, + WorkingNorms: norms, + ContextFiles: contexts, + Context: contextRefs, + ContextObjects: r.resolveContextObjects(contextRefs), + AllowedContextTypes: allowedContextTypes, + Skills: sortedSkills, + Provenance: provenance, } r.slotDepths[id] = slotDepths r.skillDepths[id] = skillDepths @@ -346,6 +406,9 @@ func (r *Resolver) mergeSkills(id string, current *resource.Document, embedded [ result := map[string]ResolvedSkill{} depths := map[string]int{} + // sealedBy records the embedded component that sealed a capability, so a + // shallower or local binding overriding it is a compile error (ADR-0016). + sealedBy := map[string]string{} for _, capabilityID := range order { group := candidates[capabilityID] minDepth := group[0].depth @@ -371,6 +434,18 @@ func (r *Resolver) mergeSkills(id string, current *resource.Document, embedded [ } result[capabilityID] = nearest[0].skill depths[capabilityID] = minDepth + // A sealed candidate may not be overridden by a shallower binding: the + // chosen (nearest) skill must be the sealed one itself. + for _, c := range group { + if c.skill.Sealed { + sealedBy[capabilityID] = c.agent + if result[capabilityID].ImplementationID != c.skill.ImplementationID { + return nil, nil, resource.Errorf( + "%s: capability '%s' is sealed by %s and cannot be overridden", + id, capabilityID, c.agent) + } + } + } } for _, binding := range current.Skills { @@ -382,6 +457,11 @@ func (r *Resolver) mergeSkills(id string, current *resource.Document, embedded [ if err != nil { return nil, nil, err } + if source, sealed := sealedBy[capabilityID]; sealed { + return nil, nil, resource.Errorf( + "%s: capability '%s' is sealed by %s and cannot be rebound", + id, capabilityID, source) + } capability, err := r.require(capabilityID, "capability") if err != nil { return nil, nil, err @@ -402,14 +482,25 @@ func (r *Resolver) mergeSkills(id string, current *resource.Document, embedded [ if err != nil { return nil, nil, err } + instructions := implementation.Instructions + defaultInstructions, variants := resolveVariants(implementation.Variants) + if variants != nil { + instructions = defaultInstructions + } result[capabilityID] = ResolvedSkill{ - CapabilityID: capabilityID, - ImplementationID: implementation.ID, - Description: implementation.Description, - Instructions: implementation.Instructions, - InputSchema: inputSchema, - OutputSchema: outputSchema, - ContextFiles: distinct(append(append([]string{}, contexts...), normalizeAll(implementation.ContextFiles)...)), + CapabilityID: capabilityID, + ImplementationID: implementation.ID, + Description: implementation.Description, + Instructions: instructions, + Variants: variants, + InputSchema: inputSchema, + OutputSchema: outputSchema, + ContextFiles: distinct(append(append([]string{}, contexts...), normalizeAll(implementation.ContextFiles)...)), + RequiresContextTypes: aggregateContextRequirements(implementation), + RequiresTools: aggregateToolRequirements(implementation), + Exposed: capability.Visibility == "exposed", + Sealed: binding.Sealed, + Required: binding.Required, Provenance: []ProvenanceEntry{ {Field: "skill.capability", Source: capabilityID}, {Field: "skill.implementation", Source: implementation.ID}, @@ -500,6 +591,336 @@ func satisfiesContract(contract *interfaceContract, slots map[string]string, ski return true } +// contextTypeClosure returns the set of contextType ids a context object of the +// given type satisfies: the type itself plus every type it transitively embeds +// (refinement). A governedX that embeds X satisfies both (ADR-0013). +func (r *Resolver) contextTypeClosure(id string, visiting map[string]bool) ([]string, error) { + ct, ok := r.resources[id] + if !ok || ct.Kind != "contextType" { + return nil, resource.Errorf("Missing contextType: %s", id) + } + if visiting[id] { + return nil, resource.Errorf("ContextType refinement cycle detected at %s", id) + } + visiting[id] = true + defer delete(visiting, id) + result := []string{id} + for _, embedID := range ct.Embeds { + base, err := r.contextTypeClosure(embedID, visiting) + if err != nil { + return nil, err + } + result = append(result, base...) + } + return distinct(result), nil +} + +// providedContextTypes is the union of contextType closures over the context +// objects an agent holds by id. +func (r *Resolver) providedContextTypes(objectIDs []string) (map[string]bool, error) { + provided := map[string]bool{} + for _, objID := range objectIDs { + obj, ok := r.resources[objID] + if !ok || obj.Kind != "context" { + return nil, resource.Errorf("Missing context: %s", objID) + } + closure, err := r.contextTypeClosure(obj.ContextType, map[string]bool{}) + if err != nil { + return nil, err + } + for _, t := range closure { + provided[t] = true + } + } + return provided, nil +} + +// validateContextFields checks a context object against its contextType schema +// (and every type it refines): required fields are present, and each declared +// field's structural type matches (ADR-0013). +func (r *Resolver) validateContextFields(obj *resource.Document) error { + closure, err := r.contextTypeClosure(obj.ContextType, map[string]bool{}) + if err != nil { + return err + } + required := map[string]bool{} + propTypes := map[string]string{} + for _, ctID := range closure { + req, types, err := parseContextSchema(r.resources[ctID].Schema) + if err != nil { + return resource.Errorf("%s: contextType %s has an invalid schema: %s", obj.ID, ctID, err) + } + for _, field := range req { + required[field] = true + } + for field, t := range types { + propTypes[field] = t + } + } + for _, name := range sortedStringSet(required) { + if _, ok := obj.ContextFields[name]; !ok { + return resource.Errorf("%s: missing required field %q declared by its contextType schema", obj.ID, name) + } + } + for _, name := range sortedStringMapKeys(propTypes) { + field, ok := obj.ContextFields[name] + if !ok { + continue // absent optional field; presence is handled above + } + if !kindMatchesType(field.Kind, propTypes[name]) { + return resource.Errorf("%s: field %q must be %s per its contextType schema, got a %s", + obj.ID, name, propTypes[name], field.Kind) + } + } + return nil +} + +// parseContextSchema reads a JSON Schema's top-level "required" names and +// property types. A malformed "required" or "properties" is an error rather +// than a silently-empty result. +func parseContextSchema(schema string) ([]string, map[string]string, error) { + if strings.TrimSpace(schema) == "" { + return nil, nil, nil + } + var doc struct { + Required []string `json:"required"` + Properties map[string]struct { + Type string `json:"type"` + } `json:"properties"` + } + if err := json.Unmarshal([]byte(schema), &doc); err != nil { + return nil, nil, resource.Errorf("\"required\" must be an array of field names and \"properties\" a type map") + } + types := map[string]string{} + for name, prop := range doc.Properties { + if prop.Type != "" { + types[name] = prop.Type + } + } + return doc.Required, types, nil +} + +// kindMatchesType reports whether a field's structural kind satisfies a JSON +// Schema type. Unknown/absent types are not enforced. +func kindMatchesType(kind, schemaType string) bool { + switch schemaType { + case "array": + return kind == "sequence" + case "object": + return kind == "mapping" + case "string", "number", "integer", "boolean": + return kind == "scalar" + default: + return true + } +} + +func sortedStringSet(m map[string]bool) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +func sortedStringMapKeys(m map[string]string) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// validateTool checks a tool declaration's interface schemas parse (ADR-0017). +func (r *Resolver) validateTool(tool *resource.Document) error { + if _, err := canonicalJSON(tool.InputSchema); err != nil { + return resource.Errorf("%s: invalid tool inputSchema: %s", tool.ID, err) + } + if _, err := canonicalJSON(tool.OutputSchema); err != nil { + return resource.Errorf("%s: invalid tool outputSchema: %s", tool.ID, err) + } + return nil +} + +// checkSkillDependencies verifies, at agent level, that every resolved skill's +// required context types are provided by held context and its required tools are +// declared (ADR-0013, ADR-0017). +func (r *Resolver) checkSkillDependencies(agentID string, skills map[string]ResolvedSkill, contextRefs []string) error { + provided, err := r.providedContextTypes(contextRefs) + if err != nil { + return err + } + capabilityIDs := make([]string, 0, len(skills)) + for capabilityID := range skills { + capabilityIDs = append(capabilityIDs, capabilityID) + } + sort.Strings(capabilityIDs) + for _, capabilityID := range capabilityIDs { + skill := skills[capabilityID] + for _, required := range skill.RequiresContextTypes { + if !provided[required] { + return resource.Errorf("%s: skill %s requires context type %s, which no held context provides", + agentID, skill.ImplementationID, required) + } + } + for _, toolID := range skill.RequiresTools { + if _, err := r.require(toolID, "tool"); err != nil { + return resource.Errorf("%s: skill %s requires tool %s, which is not declared", + agentID, skill.ImplementationID, toolID) + } + } + } + return nil +} + +// resolveVariants turns authored variants into a mode->instructions map and +// selects the default (neutral) rendering. The default preference is +// pipeline > manual > a2a, falling back to the alphabetically-first mode; a +// target adapter may later select a surface-appropriate variant (ADR-0012). +func resolveVariants(v map[string]resource.Variant) (string, map[string]string) { + if len(v) == 0 { + return "", nil + } + resolved := map[string]string{} + names := make([]string, 0, len(v)) + for name := range v { + resolved[name] = v[name].Instructions + names = append(names, name) + } + sort.Strings(names) + for _, pref := range []string{"pipeline", "manual", "a2a"} { + if ins, ok := resolved[pref]; ok { + return ins, resolved + } + } + return resolved[names[0]], resolved +} + +// aggregateContextRequirements unions a skill's context-type requirements with +// every variant's, since a multimodal skill emits all of its variants and the +// agent must satisfy each (ADR-0012 per-variant narrowing; ADR-0013). +func aggregateContextRequirements(impl *resource.Document) []string { + reqs := append([]string{}, impl.RequiresContextTypes...) + for _, mode := range sortedKeys(impl.Variants) { + reqs = append(reqs, impl.Variants[mode].RequiresContextTypes...) + } + return distinct(reqs) +} + +func aggregateToolRequirements(impl *resource.Document) []string { + reqs := append([]string{}, impl.RequiresTools...) + for _, mode := range sortedKeys(impl.Variants) { + reqs = append(reqs, impl.Variants[mode].RequiresTools...) + } + return distinct(reqs) +} + +func sortedKeys(m map[string]resource.Variant) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// intersectAllowLists computes the effective allowedContextTypes for a +// component: the intersection of every non-empty allow-list among itself and +// its embedded components (empty result means unrestricted; ADR-0013). +func intersectAllowLists(embedded []*ResolvedAgent, current *resource.Document) []string { + lists := [][]string{} + for _, c := range embedded { + if len(c.AllowedContextTypes) > 0 { + lists = append(lists, c.AllowedContextTypes) + } + } + if len(current.AllowedContextTypes) > 0 { + lists = append(lists, current.AllowedContextTypes) + } + if len(lists) == 0 { + return nil + } + result := append([]string{}, lists[0]...) + for _, l := range lists[1:] { + set := map[string]bool{} + for _, x := range l { + set[x] = true + } + filtered := result[:0:0] + for _, x := range result { + if set[x] { + filtered = append(filtered, x) + } + } + result = filtered + } + return distinct(result) +} + +// checkAllowedContext verifies each held context object satisfies at least one +// allowed contextType (through its refinement closure). Unrestricted when the +// effective allow-list is empty (ADR-0013). +func (r *Resolver) checkAllowedContext(agentID string, contextRefs, allowed []string) error { + if len(allowed) == 0 { + return nil + } + allowSet := map[string]bool{} + for _, a := range allowed { + allowSet[a] = true + } + for _, objID := range contextRefs { + obj, ok := r.resources[objID] + if !ok || obj.Kind != "context" { + continue // existence is checked in providedContextTypes + } + closure, err := r.contextTypeClosure(obj.ContextType, map[string]bool{}) + if err != nil { + return err + } + permitted := false + for _, t := range closure { + if allowSet[t] { + permitted = true + break + } + } + if !permitted { + return resource.Errorf("%s: held context %s (type %s) is not among the allowed context types", + agentID, objID, obj.ContextType) + } + } + return nil +} + +// resolveContextObjects turns held context ids into (id, contextType) refs, +// sorted by id for deterministic emission. Ids that do not resolve to a context +// object are skipped (agent-level checks surface missing context elsewhere). +func (r *Resolver) resolveContextObjects(contextRefs []string) []ResolvedContextRef { + refs := []ResolvedContextRef{} + for _, id := range contextRefs { + if obj, ok := r.resources[id]; ok && obj.Kind == "context" { + refs = append(refs, ResolvedContextRef{ + ID: id, + DisplayName: obj.DisplayName, + ContextType: obj.ContextType, + Content: obj.Content, + }) + } + } + sort.Slice(refs, func(i, j int) bool { return refs[i].ID < refs[j].ID }) + return refs +} + +func concatContextRefs(embedded []*ResolvedAgent, current *resource.Document) []string { + values := []string{} + for _, component := range embedded { + values = append(values, component.Context...) + } + return append(values, current.Context...) +} + func (r *Resolver) require(id, kind string) (*resource.Document, error) { doc, ok := r.resources[id] if !ok || doc.Kind != kind { @@ -565,6 +986,30 @@ func withDispatch(skill ResolvedSkill, agentID string) ResolvedSkill { return skill } +// InstructionsFor returns the instructions for an invocation mode: the variant's +// rendering when this is a multimodal skill that declares the mode, otherwise the +// default Instructions (ADR-0012). Lets a surface pick its face — e.g. a callable +// card selects the a2a variant. +func (s ResolvedSkill) InstructionsFor(mode string) string { + if ins, ok := s.Variants[mode]; ok { + return ins + } + return s.Instructions +} + +// ExposedSkills returns the resolved skills whose capability is exposed, in +// dispatch order: the agent's public callable surface (ADR-0015). A callable +// card (ADR-0018) is emitted from exactly these, not from every skill. +func (a *ResolvedAgent) ExposedSkills() []ResolvedSkill { + out := []ResolvedSkill{} + for _, s := range a.Skills { + if s.Exposed { + out = append(out, s) + } + } + return out +} + // Leaf extracts the unversioned name segment of a resource id // (namespace/name@version -> name). func Leaf(id string) string { diff --git a/go/internal/resolve/schema_fields_test.go b/go/internal/resolve/schema_fields_test.go new file mode 100644 index 0000000..e6d41ef --- /dev/null +++ b/go/internal/resolve/schema_fields_test.go @@ -0,0 +1,82 @@ +package resolve + +import ( + "strings" + "testing" + + "github.com/buchk/TypeFerence/go/internal/resource" +) + +func TestContextMissingRequiredFieldRejected(t *testing.T) { + ct := doc("contextType", "t/ct/cast@1.0.0", func(d *resource.Document) { + d.Schema = `{"type":"object","required":["role"]}` + }) + obj := doc("context", "t/notes/n@1.0.0", func(d *resource.Document) { d.ContextType = "t/ct/cast@1.0.0" }) + _, err := New(docSet(ct, obj)).ResolveAll() + if err == nil || !strings.Contains(err.Error(), "missing required field") { + t.Fatalf("expected a missing-required-field error, got %v", err) + } +} + +func TestContextRequiredFieldPresent(t *testing.T) { + ct := doc("contextType", "t/ct/cast@1.0.0", func(d *resource.Document) { + d.Schema = `{"type":"object","required":["role"]}` + }) + obj := doc("context", "t/notes/n@1.0.0", func(d *resource.Document) { + d.ContextType = "t/ct/cast@1.0.0" + d.ContextFields = map[string]resource.FieldValue{"role": {Kind: "scalar", Scalar: "owner"}} + }) + if _, err := New(docSet(ct, obj)).ResolveAll(); err != nil { + t.Fatalf("a present required field should validate: %v", err) + } +} + +func TestContextFieldTypeMismatchRejected(t *testing.T) { + ct := doc("contextType", "t/ct/cast@1.0.0", func(d *resource.Document) { + d.Schema = `{"type":"object","properties":{"tags":{"type":"array"}}}` + }) + obj := doc("context", "t/notes/n@1.0.0", func(d *resource.Document) { + d.ContextType = "t/ct/cast@1.0.0" + d.ContextFields = map[string]resource.FieldValue{"tags": {Kind: "scalar", Scalar: "not-an-array"}} + }) + _, err := New(docSet(ct, obj)).ResolveAll() + if err == nil || !strings.Contains(err.Error(), "must be array") { + t.Fatalf("a scalar in an array-typed field must be rejected, got %v", err) + } +} + +func TestContextFieldTypeMatchAccepted(t *testing.T) { + ct := doc("contextType", "t/ct/cast@1.0.0", func(d *resource.Document) { + d.Schema = `{"type":"object","properties":{"tags":{"type":"array"},"owner":{"type":"string"}}}` + }) + obj := doc("context", "t/notes/n@1.0.0", func(d *resource.Document) { + d.ContextType = "t/ct/cast@1.0.0" + d.ContextFields = map[string]resource.FieldValue{ + "tags": {Kind: "sequence"}, + "owner": {Kind: "scalar", Scalar: "Dana"}, + } + }) + if _, err := New(docSet(ct, obj)).ResolveAll(); err != nil { + t.Fatalf("matching field types should validate: %v", err) + } +} + +func TestRequiredFieldFromRefinedTypeEnforced(t *testing.T) { + base := doc("contextType", "t/ct/cast@1.0.0", func(d *resource.Document) { + d.Schema = `{"type":"object","required":["role"]}` + }) + gov := doc("contextType", "t/ct/gov@1.0.0", func(d *resource.Document) { d.Embeds = []string{"t/ct/cast@1.0.0"} }) + obj := doc("context", "t/notes/n@1.0.0", func(d *resource.Document) { d.ContextType = "t/ct/gov@1.0.0" }) + _, err := New(docSet(base, gov, obj)).ResolveAll() + if err == nil || !strings.Contains(err.Error(), "missing required field") { + t.Fatalf("a required field from a refined type must be enforced, got %v", err) + } +} + +func TestSchemalessContextTypeImposesNoFields(t *testing.T) { + ct := doc("contextType", "t/ct/cast@1.0.0", nil) // no schema + obj := doc("context", "t/notes/n@1.0.0", func(d *resource.Document) { d.ContextType = "t/ct/cast@1.0.0" }) + if _, err := New(docSet(ct, obj)).ResolveAll(); err != nil { + t.Fatalf("a schemaless contextType should impose no field requirements: %v", err) + } +} diff --git a/go/internal/resolve/sealing_test.go b/go/internal/resolve/sealing_test.go new file mode 100644 index 0000000..c60577b --- /dev/null +++ b/go/internal/resolve/sealing_test.go @@ -0,0 +1,78 @@ +package resolve + +import ( + "strings" + "testing" + + "github.com/buchk/TypeFerence/go/internal/resource" +) + +// sealedSetup: a deep profile that seals capability C with skill s1. +func sealedSetup() map[string]*resource.Document { + capC := doc("capability", "t/cap/c@1.0.0", nil) + s1 := doc("skill", "t/skills/s1@1.0.0", func(d *resource.Document) { d.Binds = "t/cap/c@1.0.0" }) + deep := doc("profile", "t/deep@1.0.0", func(d *resource.Document) { + d.Skills = []resource.SkillBinding{{Ref: "t/skills/s1@1.0.0", Sealed: true}} + }) + return docSet(capC, s1, deep) +} + +func TestSealedCapabilityCannotBeReboundLocally(t *testing.T) { + set := sealedSetup() + set["t/skills/s2@1.0.0"] = doc("skill", "t/skills/s2@1.0.0", func(d *resource.Document) { d.Binds = "t/cap/c@1.0.0" }) + set["t/agent@1.0.0"] = doc("agent", "t/agent@1.0.0", func(d *resource.Document) { + d.Embeds = []string{"t/deep@1.0.0"} + d.Skills = []resource.SkillBinding{{Ref: "t/skills/s2@1.0.0"}} // rebinds sealed C + }) + _, err := New(set).Resolve("t/agent@1.0.0") + if err == nil || !strings.Contains(err.Error(), "sealed") { + t.Fatalf("expected sealed-rebind error, got %v", err) + } +} + +func TestSealedCapabilityAllowsAdjacentExtension(t *testing.T) { + set := sealedSetup() + set["t/cap/d@1.0.0"] = doc("capability", "t/cap/d@1.0.0", nil) + set["t/skills/d@1.0.0"] = doc("skill", "t/skills/d@1.0.0", func(d *resource.Document) { d.Binds = "t/cap/d@1.0.0" }) + set["t/agent@1.0.0"] = doc("agent", "t/agent@1.0.0", func(d *resource.Document) { + d.Embeds = []string{"t/deep@1.0.0"} + d.Skills = []resource.SkillBinding{{Ref: "t/skills/d@1.0.0"}} // adjacent capability, not an override + }) + resolved, err := New(set).Resolve("t/agent@1.0.0") + if err != nil { + t.Fatalf("adding an adjacent capability alongside a sealed one must be allowed: %v", err) + } + if len(resolved.Skills) != 2 { + t.Errorf("expected the sealed capability plus the added one, got %d skills", len(resolved.Skills)) + } +} + +func TestSealedCapabilityShallowerOverrideRejected(t *testing.T) { + // A mid profile embeds the sealing profile and tries to rebind C: rejected. + set := sealedSetup() + set["t/skills/s2@1.0.0"] = doc("skill", "t/skills/s2@1.0.0", func(d *resource.Document) { d.Binds = "t/cap/c@1.0.0" }) + set["t/mid@1.0.0"] = doc("profile", "t/mid@1.0.0", func(d *resource.Document) { + d.Embeds = []string{"t/deep@1.0.0"} + d.Skills = []resource.SkillBinding{{Ref: "t/skills/s2@1.0.0"}} + }) + _, err := New(set).ResolveAll() + if err == nil || !strings.Contains(err.Error(), "sealed") { + t.Fatalf("expected sealed-override error at the mid profile, got %v", err) + } +} + +func TestSealedCapabilityUntouchedResolves(t *testing.T) { + // Embedding a sealing profile without touching the sealed capability is fine; + // the sealed skill is carried through. + set := sealedSetup() + set["t/agent@1.0.0"] = doc("agent", "t/agent@1.0.0", func(d *resource.Document) { + d.Embeds = []string{"t/deep@1.0.0"} + }) + resolved, err := New(set).Resolve("t/agent@1.0.0") + if err != nil { + t.Fatal(err) + } + if len(resolved.Skills) != 1 || !resolved.Skills[0].Sealed { + t.Errorf("expected one carried sealed skill, got %+v", resolved.Skills) + } +} diff --git a/go/internal/resolve/typed_context_test.go b/go/internal/resolve/typed_context_test.go new file mode 100644 index 0000000..93c97ca --- /dev/null +++ b/go/internal/resolve/typed_context_test.go @@ -0,0 +1,117 @@ +package resolve + +import ( + "strings" + "testing" + + "github.com/buchk/TypeFerence/go/internal/resource" +) + +// baseSkillAgent returns a capability + skill + agent that resolve cleanly, so +// tests can layer context/tool requirements onto the skill. +func baseSkillAgent(mutateSkill, mutateAgent func(*resource.Document)) map[string]*resource.Document { + cap := doc("capability", "t/cap/c@1.0.0", nil) + skill := doc("skill", "t/skills/s@1.0.0", func(d *resource.Document) { + d.Binds = "t/cap/c@1.0.0" + if mutateSkill != nil { + mutateSkill(d) + } + }) + agent := doc("agent", "t/agent@1.0.0", func(d *resource.Document) { + d.Skills = []resource.SkillBinding{{Ref: "t/skills/s@1.0.0"}} + if mutateAgent != nil { + mutateAgent(d) + } + }) + return docSet(cap, skill, agent) +} + +func TestRequiresContextTypeSatisfiedByRefinement(t *testing.T) { + set := baseSkillAgent( + func(s *resource.Document) { s.RequiresContextTypes = []string{"t/ct/cast@1.0.0"} }, + func(a *resource.Document) { a.Context = []string{"t/notes/roster@1.0.0"} }, + ) + set["t/ct/cast@1.0.0"] = doc("contextType", "t/ct/cast@1.0.0", nil) + set["t/ct/governed@1.0.0"] = doc("contextType", "t/ct/governed@1.0.0", func(d *resource.Document) { + d.Embeds = []string{"t/ct/cast@1.0.0"} // governed refines cast + }) + set["t/notes/roster@1.0.0"] = doc("context", "t/notes/roster@1.0.0", func(d *resource.Document) { + d.ContextType = "t/ct/governed@1.0.0" // holds the refinement + }) + if _, err := New(set).Resolve("t/agent@1.0.0"); err != nil { + t.Fatalf("a held refinement should satisfy a base-type requirement: %v", err) + } +} + +func TestRequiresContextTypeUnsatisfied(t *testing.T) { + set := baseSkillAgent( + func(s *resource.Document) { s.RequiresContextTypes = []string{"t/ct/cast@1.0.0"} }, + nil, // agent holds no context + ) + set["t/ct/cast@1.0.0"] = doc("contextType", "t/ct/cast@1.0.0", nil) + _, err := New(set).Resolve("t/agent@1.0.0") + if err == nil || !strings.Contains(err.Error(), "requires context type") { + t.Fatalf("expected unsatisfied context-type error, got %v", err) + } +} + +func TestContextRequirementIsAgentLevelNotProfileLevel(t *testing.T) { + // A profile binding a context-requiring skill must not error on its own; the + // requirement is checked once the agent supplies the context. + cap := doc("capability", "t/cap/c@1.0.0", nil) + skill := doc("skill", "t/skills/s@1.0.0", func(d *resource.Document) { + d.Binds = "t/cap/c@1.0.0" + d.RequiresContextTypes = []string{"t/ct/cast@1.0.0"} + }) + profile := doc("profile", "t/profile@1.0.0", func(d *resource.Document) { + d.Skills = []resource.SkillBinding{{Ref: "t/skills/s@1.0.0"}} + }) + ct := doc("contextType", "t/ct/cast@1.0.0", nil) + obj := doc("context", "t/notes/n@1.0.0", func(d *resource.Document) { d.ContextType = "t/ct/cast@1.0.0" }) + agent := doc("agent", "t/agent@1.0.0", func(d *resource.Document) { + d.Embeds = []string{"t/profile@1.0.0"} + d.Context = []string{"t/notes/n@1.0.0"} + }) + if _, err := New(docSet(cap, skill, profile, ct, obj, agent)).ResolveAll(); err != nil { + t.Fatalf("agent that supplies context should resolve the promoted requirement: %v", err) + } +} + +func TestRequiresToolDeclared(t *testing.T) { + set := baseSkillAgent( + func(s *resource.Document) { s.RequiresTools = []string{"t/tools/reader@1.0.0"} }, + nil, + ) + set["t/tools/reader@1.0.0"] = doc("tool", "t/tools/reader@1.0.0", nil) + if _, err := New(set).Resolve("t/agent@1.0.0"); err != nil { + t.Fatalf("a declared tool should satisfy requiresTools: %v", err) + } +} + +func TestRequiresToolUndeclared(t *testing.T) { + set := baseSkillAgent( + func(s *resource.Document) { s.RequiresTools = []string{"t/tools/missing@1.0.0"} }, + nil, + ) + _, err := New(set).Resolve("t/agent@1.0.0") + if err == nil || !strings.Contains(err.Error(), "requires tool") { + t.Fatalf("expected undeclared-tool error, got %v", err) + } +} + +func TestContextTypeRefinementCycleRejected(t *testing.T) { + a := doc("contextType", "t/ct/a@1.0.0", func(d *resource.Document) { d.Embeds = []string{"t/ct/b@1.0.0"} }) + b := doc("contextType", "t/ct/b@1.0.0", func(d *resource.Document) { d.Embeds = []string{"t/ct/a@1.0.0"} }) + _, err := New(docSet(a, b)).ResolveAll() + if err == nil || !strings.Contains(err.Error(), "refinement cycle") { + t.Fatalf("expected refinement cycle error, got %v", err) + } +} + +func TestToolInvalidSchemaRejected(t *testing.T) { + tool := doc("tool", "t/tools/reader@1.0.0", func(d *resource.Document) { d.InputSchema = "not json" }) + _, err := New(docSet(tool)).ResolveAll() + if err == nil || !strings.Contains(err.Error(), "tool inputSchema") { + t.Fatalf("expected invalid tool schema error, got %v", err) + } +} diff --git a/go/internal/resolve/variants_test.go b/go/internal/resolve/variants_test.go new file mode 100644 index 0000000..b472129 --- /dev/null +++ b/go/internal/resolve/variants_test.go @@ -0,0 +1,75 @@ +package resolve + +import ( + "testing" + + "github.com/buchk/TypeFerence/go/internal/resource" +) + +func TestVariantsResolveWithPreferredDefault(t *testing.T) { + set := baseSkillAgent(func(s *resource.Document) { + s.Variants = map[string]resource.Variant{ + "pipeline": {Instructions: "strict json"}, + "manual": {Instructions: "explain"}, + "a2a": {Instructions: "attributed"}, + } + }, nil) + agent, err := New(set).Resolve("t/agent@1.0.0") + if err != nil { + t.Fatal(err) + } + skill := agent.Skills[0] + if skill.Instructions != "strict json" { + t.Errorf("default rendering should prefer pipeline, got %q", skill.Instructions) + } + if len(skill.Variants) != 3 || skill.Variants["manual"] != "explain" || skill.Variants["a2a"] != "attributed" { + t.Errorf("variants not carried through resolution: %+v", skill.Variants) + } +} + +func TestVariantsDefaultFallsBackAlphabetically(t *testing.T) { + set := baseSkillAgent(func(s *resource.Document) { + s.Variants = map[string]resource.Variant{ + "beta": {Instructions: "b"}, + "alpha": {Instructions: "a"}, + } + }, nil) + agent, err := New(set).Resolve("t/agent@1.0.0") + if err != nil { + t.Fatal(err) + } + if agent.Skills[0].Instructions != "a" { + t.Errorf("with no preferred mode, default should be alphabetically-first 'alpha', got %q", agent.Skills[0].Instructions) + } +} + +func TestInstructionsForSelectsVariantOrDefault(t *testing.T) { + s := ResolvedSkill{ + Instructions: "default", + Variants: map[string]string{"a2a": "attributed", "manual": "explain"}, + } + if got := s.InstructionsFor("a2a"); got != "attributed" { + t.Errorf("InstructionsFor(a2a) = %q, want attributed", got) + } + if got := s.InstructionsFor("nope"); got != "default" { + t.Errorf("InstructionsFor(missing mode) should fall back to default, got %q", got) + } + uni := ResolvedSkill{Instructions: "only"} + if got := uni.InstructionsFor("a2a"); got != "only" { + t.Errorf("unimodal InstructionsFor should return Instructions, got %q", got) + } +} + +func TestUnimodalSkillHasNoVariants(t *testing.T) { + set := baseSkillAgent(func(s *resource.Document) { s.Instructions = "just do it" }, nil) + agent, err := New(set).Resolve("t/agent@1.0.0") + if err != nil { + t.Fatal(err) + } + if agent.Skills[0].Variants != nil { + t.Errorf("unimodal skill should carry no variants map") + } + if agent.Skills[0].Instructions != "just do it" { + t.Errorf("unimodal instructions changed: %q", agent.Skills[0].Instructions) + } +} diff --git a/go/internal/resource/loader.go b/go/internal/resource/loader.go index 9b819bf..6a16c33 100644 --- a/go/internal/resource/loader.go +++ b/go/internal/resource/loader.go @@ -37,7 +37,10 @@ func Load(sourceDir string, trustConfigPath string) (map[string]*Document, error if info, statErr := os.Stat(root); statErr != nil || !info.IsDir() { return nil, Errorf("Source directory not found: %s", root) } - excluded := []string{filepath.Join(root, "typeference.trust.yaml")} + excluded := []string{ + filepath.Join(root, "typeference.trust.yaml"), + filepath.Join(root, ProjectManifestFile), + } if trustConfigPath != "" { if full, absErr := filepath.Abs(trustConfigPath); absErr == nil { excluded = append(excluded, full) @@ -48,7 +51,8 @@ func Load(sourceDir string, trustConfigPath string) (map[string]*Document, error if err != nil { return err } - if d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") { + name := d.Name() + if d.IsDir() || (!strings.HasSuffix(name, ".yaml") && !strings.HasSuffix(name, ".tfer")) { return nil } for _, ex := range excluded { @@ -70,13 +74,28 @@ func Load(sourceDir string, trustConfigPath string) (map[string]*Document, error if readErr != nil { return nil, Errorf("%s: %s", file, readErr) } - doc, parseErr := parseDocument(stripBOM(string(raw))) + text := stripBOM(string(raw)) + isTfer := strings.HasSuffix(file, ".tfer") + var body string + if isTfer { + frontmatter, tferBody, splitErr := splitFrontmatter(text) + if splitErr != nil { + return nil, Errorf("%s: %s", file, splitErr) + } + text, body = frontmatter, tferBody + } + doc, parseErr := parseDocument(text) if parseErr != nil { return nil, Errorf("%s: invalid YAML resource: %s", file, parseErr) } if doc == nil { return nil, Errorf("Empty resource: %s", file) } + if isTfer { + if err := applyBody(doc, body, file); err != nil { + return nil, err + } + } if err := validateShape(doc, file, root); err != nil { return nil, err } @@ -121,6 +140,17 @@ func parseDocument(text string) (*Document, error) { return nil, Errorf("expected a single YAML document") } doc := NewDocument() + // A context object may carry schema-typed frontmatter fields beyond the + // standard keys; every other kind stays strict (unknown key => error). + kind := scanKind(&node) + extraFields := map[string]*yaml.Node{} + unknown := func(key string, value *yaml.Node) error { + if kind == "context" { + extraFields[key] = value + return nil + } + return Errorf("property '%s' not found", key) + } if err := decodeMapping(&node, map[string]fieldDecoder{ "schemaVersion": intField(&doc.SchemaVersion), "kind": stringField(&doc.Kind), @@ -139,12 +169,108 @@ func parseDocument(text string) (*Document, error) { "instructions": stringField(&doc.Instructions), "inputSchema": stringField(&doc.InputSchema), "outputSchema": stringField(&doc.OutputSchema), - }); err != nil { + "contextType": stringField(&doc.ContextType), + "schema": stringField(&doc.Schema), + "requiresContextTypes": stringListField(&doc.RequiresContextTypes), + "context": stringListField(&doc.Context), + "requiresTools": stringListField(&doc.RequiresTools), + "visibility": stringField(&doc.Visibility), + "variants": variantsField(&doc.Variants), + "allowedContextTypes": stringListField(&doc.AllowedContextTypes), + }, unknown); err != nil { return nil, err } + if len(extraFields) > 0 { + doc.ContextFields = map[string]FieldValue{} + for key, value := range extraFields { + switch value.Kind { + case yaml.SequenceNode: + doc.ContextFields[key] = FieldValue{Kind: "sequence"} + case yaml.MappingNode: + doc.ContextFields[key] = FieldValue{Kind: "mapping"} + default: + doc.ContextFields[key] = FieldValue{Kind: "scalar", Scalar: value.Value} + } + } + } return doc, nil } +// scanKind reads the top-level `kind` scalar without a full decode, so the +// decoder can decide whether unknown frontmatter keys are context fields or an +// error. +func scanKind(node *yaml.Node) string { + n := resolveAlias(node) + if n.Kind == yaml.DocumentNode && len(n.Content) == 1 { + n = resolveAlias(n.Content[0]) + } + if n.Kind != yaml.MappingNode { + return "" + } + for i := 0; i+1 < len(n.Content); i += 2 { + key := resolveAlias(n.Content[i]) + if key.Kind == yaml.ScalarNode && key.Value == "kind" { + if v := resolveAlias(n.Content[i+1]); v.Kind == yaml.ScalarNode { + return v.Value + } + } + } + return "" +} + +// splitFrontmatter separates a .tfer file into its YAML frontmatter and its +// verbatim markdown body. The file MUST begin with a `---` fence line; the +// frontmatter runs to the next `---` fence line and the body is everything +// after it, preserved byte-for-byte (ADR-0013 format: typed head, prose tail). +func splitFrontmatter(text string) (frontmatter, body string, err error) { + nl := strings.IndexByte(text, '\n') + if nl < 0 || strings.TrimRight(text[:nl], "\r") != "---" { + return "", "", Errorf("a .tfer file must begin with a '---' frontmatter fence") + } + rest := text[nl+1:] + for idx := 0; ; { + lineEnd := strings.IndexByte(rest[idx:], '\n') + if lineEnd < 0 { + if strings.TrimRight(rest[idx:], "\r") == "---" { + return rest[:idx], "", nil + } + return "", "", Errorf("a .tfer file is missing its closing '---' frontmatter fence") + } + line := rest[idx : idx+lineEnd] + next := idx + lineEnd + 1 + if strings.TrimRight(line, "\r") == "---" { + return rest[:idx], rest[next:], nil + } + idx = next + } +} + +// applyBody materializes a .tfer markdown body onto the resource: a skill's +// instructions or a context object's content. Kinds without a body field +// reject a non-empty body (ADR-0013 format; ADR-0012 for the multimodal note). +func applyBody(doc *Document, body, file string) error { + switch doc.Kind { + case "skill": + if strings.TrimSpace(body) == "" { + return nil + } + if len(doc.Variants) != 0 { + return Errorf("%s: a multimodal skill has no single body; put instructions inside each variant", file) + } + if strings.TrimSpace(doc.Instructions) != "" { + return Errorf("%s: a .tfer skill sets instructions in both the body and frontmatter; use one", file) + } + doc.Instructions = body + case "context": + doc.Content = body + default: + if strings.TrimSpace(body) != "" { + return Errorf("%s: a %s resource has no body field; put content in frontmatter", file, doc.Kind) + } + } + return nil +} + type fieldDecoder func(*yaml.Node) error func resolveAlias(node *yaml.Node) *yaml.Node { @@ -154,7 +280,9 @@ func resolveAlias(node *yaml.Node) *yaml.Node { return node } -func decodeMapping(node *yaml.Node, fields map[string]fieldDecoder) error { +// decodeMapping decodes a mapping node against a field table. Unknown keys go +// to the optional unknown handler; when it is nil an unknown key is an error. +func decodeMapping(node *yaml.Node, fields map[string]fieldDecoder, unknown func(string, *yaml.Node) error) error { node = resolveAlias(node) if node.Kind == yaml.DocumentNode && len(node.Content) == 1 { node = resolveAlias(node.Content[0]) @@ -169,6 +297,12 @@ func decodeMapping(node *yaml.Node, fields map[string]fieldDecoder) error { } decoder, known := fields[key.Value] if !known { + if unknown != nil { + if err := unknown(key.Value, resolveAlias(node.Content[i+1])); err != nil { + return err + } + continue + } return Errorf("property '%s' not found", key.Value) } if err := decoder(resolveAlias(node.Content[i+1])); err != nil { @@ -267,6 +401,32 @@ func stringMapField(target *map[string]string) fieldDecoder { } } +func variantsField(target *map[string]Variant) fieldDecoder { + return func(node *yaml.Node) error { + if node.Kind != yaml.MappingNode { + return Errorf("expected a mapping") + } + m := map[string]Variant{} + for i := 0; i+1 < len(node.Content); i += 2 { + key := resolveAlias(node.Content[i]) + if key.Kind != yaml.ScalarNode { + return Errorf("mapping keys must be scalars") + } + var v Variant + if err := decodeMapping(resolveAlias(node.Content[i+1]), map[string]fieldDecoder{ + "instructions": stringField(&v.Instructions), + "requiresContextTypes": stringListField(&v.RequiresContextTypes), + "requiresTools": stringListField(&v.RequiresTools), + }, nil); err != nil { + return err + } + m[key.Value] = v + } + *target = m + return nil + } +} + func skillsField(target *[]SkillBinding) fieldDecoder { return func(node *yaml.Node) error { if node.Kind != yaml.SequenceNode { @@ -283,7 +443,9 @@ func skillsField(target *[]SkillBinding) fieldDecoder { hasCapability = true return scalarString(n, &capability) }, - }) + "sealed": boolField(&binding.Sealed), + "required": boolField(&binding.Required), + }, nil) if err != nil { return err } @@ -298,19 +460,119 @@ func skillsField(target *[]SkillBinding) fieldDecoder { } func validateShape(doc *Document, file, root string) error { + if err := validateDocumentShape(doc, file); err != nil { + return err + } + var referenced []string + referenced = append(referenced, doc.ContextFiles...) + for _, key := range SortedKeys(doc.Slots) { + referenced = append(referenced, doc.Slots[key]) + } + for _, relative := range referenced { + full := filepath.Join(root, filepath.FromSlash(relative)) + if !strings.HasPrefix(full, root+string(filepath.Separator)) { + return Errorf("%s: path escapes source root: %s", file, relative) + } + if info, err := os.Stat(full); err != nil || info.IsDir() { + return Errorf("%s: referenced file does not exist: %s", file, relative) + } + } + return nil +} + +// validateDocumentShape validates a single resource in isolation: every rule +// except the composition-level checks that require the whole source tree +// (referenced-file existence). CheckDocument reuses it for the language server. +func validateDocumentShape(doc *Document, file string) error { if doc.SchemaVersion != 3 { return Errorf("%s: schemaVersion must be 3", file) } switch doc.Kind { - case "agent", "profile", "interface", "capability", "skill": + case "agent", "profile", "interface", "capability", "skill", "context", "contextType", "tool": default: return Errorf("%s: unknown kind '%s'", file, doc.Kind) } if !resourceID.MatchString(doc.ID) { return Errorf("%s: id must use lowercase namespace/name@semantic-version", file) } - if (doc.Kind == "capability" || doc.Kind == "skill") && len(doc.Embeds) != 0 { - return Errorf("%s: %ss cannot embed resources", file, doc.Kind) + // capabilities, skills, context objects, and tools do not embed; contextTypes + // may embed to refine other contextTypes (ADR-0013), and agents/profiles/ + // interfaces embed by design. + switch doc.Kind { + case "capability", "skill", "context", "tool": + if len(doc.Embeds) != 0 { + return Errorf("%s: %ss cannot embed resources", file, doc.Kind) + } + } + if doc.Kind == "context" { + if strings.TrimSpace(doc.ContextType) == "" { + return Errorf("%s: a context resource must declare a contextType", file) + } + if !resourceID.MatchString(doc.ContextType) { + return Errorf("%s: contextType must reference a contextType id", file) + } + } else if strings.TrimSpace(doc.ContextType) != "" { + return Errorf("%s: only context resources declare a contextType", file) + } + if doc.Kind == "contextType" { + if strings.TrimSpace(doc.Schema) != "" { + if err := validateJSON(doc.Schema, file, "schema"); err != nil { + return err + } + } + } else if strings.TrimSpace(doc.Schema) != "" { + return Errorf("%s: only contextType resources declare a schema", file) + } + // requiresContextTypes / requiresTools are skill-only; context (holding by + // id) is agent/profile-only; visibility is capability-only. + if len(doc.RequiresContextTypes) != 0 && doc.Kind != "skill" { + return Errorf("%s: only skills declare requiresContextTypes", file) + } + if len(doc.RequiresTools) != 0 && doc.Kind != "skill" { + return Errorf("%s: only skills declare requiresTools", file) + } + if len(doc.Context) != 0 && doc.Kind != "agent" && doc.Kind != "profile" { + return Errorf("%s: only agents and profiles hold context by id", file) + } + if len(doc.AllowedContextTypes) != 0 && doc.Kind != "agent" && doc.Kind != "profile" { + return Errorf("%s: only agents and profiles declare allowedContextTypes", file) + } + refs := append([]string{}, doc.RequiresContextTypes...) + refs = append(refs, doc.RequiresTools...) + refs = append(refs, doc.Context...) + refs = append(refs, doc.AllowedContextTypes...) + for _, v := range doc.Variants { + refs = append(refs, v.RequiresContextTypes...) + refs = append(refs, v.RequiresTools...) + } + for _, ref := range refs { + if !resourceID.MatchString(ref) { + return Errorf("%s: reference '%s' must be a namespace/name@semantic-version id", file, ref) + } + } + if strings.TrimSpace(doc.Visibility) != "" { + if doc.Kind != "capability" { + return Errorf("%s: only capabilities declare visibility", file) + } + if doc.Visibility != "internal" && doc.Visibility != "exposed" { + return Errorf("%s: visibility must be 'internal' or 'exposed'", file) + } + } + if len(doc.Variants) != 0 { + if doc.Kind != "skill" { + return Errorf("%s: only skills declare variants", file) + } + if strings.TrimSpace(doc.Instructions) != "" { + return Errorf("%s: a skill declares either instructions or variants, not both", file) + } + for mode, v := range doc.Variants { + if !slotName.MatchString(mode) { + return Errorf("%s: variant mode '%s' must be an ASCII identifier", file, mode) + } + if strings.TrimSpace(v.Instructions) == "" { + return Errorf("%s: variant '%s' must set instructions", file, mode) + } + } } if doc.Kind == "skill" && strings.TrimSpace(doc.Binds) == "" { return Errorf("%s: skills must bind a capability", file) @@ -327,26 +589,41 @@ func validateShape(doc *Document, file, root string) error { return Errorf("%s: slot name '%s' must be an ASCII identifier matching [A-Za-z0-9][A-Za-z0-9._-]*", file, name) } } - var referenced []string - referenced = append(referenced, doc.ContextFiles...) - for _, key := range SortedKeys(doc.Slots) { - referenced = append(referenced, doc.Slots[key]) - } - for _, relative := range referenced { - full := filepath.Join(root, filepath.FromSlash(relative)) - if !strings.HasPrefix(full, root+string(filepath.Separator)) { - return Errorf("%s: path escapes source root: %s", file, relative) - } - if info, err := os.Stat(full); err != nil || info.IsDir() { - return Errorf("%s: referenced file does not exist: %s", file, relative) - } - } if err := validateJSON(doc.InputSchema, file, "inputSchema"); err != nil { return err } return validateJSON(doc.OutputSchema, file, "outputSchema") } +// CheckDocument parses one resource from its path and content and validates its +// shape in isolation, without composition-level checks that need the whole +// source tree. It is the diagnostic entrypoint used by the language server. +func CheckDocument(path, content string) error { + text := stripBOM(content) + isTfer := strings.HasSuffix(path, ".tfer") + var body string + if isTfer { + frontmatter, tferBody, err := splitFrontmatter(text) + if err != nil { + return err + } + text, body = frontmatter, tferBody + } + doc, err := parseDocument(text) + if err != nil { + return Errorf("invalid YAML resource: %s", err) + } + if doc == nil { + return Errorf("empty resource") + } + if isTfer { + if err := applyBody(doc, body, filepath.Base(path)); err != nil { + return err + } + } + return validateDocumentShape(doc, filepath.Base(path)) +} + func validateJSON(value, file, field string) error { if _, err := jsonx.Parse(value); err != nil { return Errorf("%s: invalid %s: %s", file, field, err) diff --git a/go/internal/resource/loader_test.go b/go/internal/resource/loader_test.go index d9c4f26..a01d701 100644 --- a/go/internal/resource/loader_test.go +++ b/go/internal/resource/loader_test.go @@ -176,3 +176,149 @@ func TestUnknownKindRejected(t *testing.T) { t.Fatalf("expected kind error, got %v", err) } } + +const tferSkill = `--- +schemaVersion: 3 +kind: skill +id: t/skills/s@1.0.0 +binds: t/capabilities/c@1.0.0 +--- +Inspect the requested signals and report status, evidence, and risk. +` + +func TestTferSkillBodyBecomesInstructions(t *testing.T) { + root := writeSource(t, map[string]string{"s.tfer": tferSkill}) + docs, err := Load(root, "") + if err != nil { + t.Fatal(err) + } + doc := docs["t/skills/s@1.0.0"] + if doc == nil { + t.Fatal("skill not loaded") + } + want := "Inspect the requested signals and report status, evidence, and risk.\n" + if doc.Instructions != want { + t.Errorf("body did not become instructions verbatim: got %q want %q", doc.Instructions, want) + } +} + +func TestTferInstructionsInBothBodyAndFrontmatterRejected(t *testing.T) { + dual := strings.Replace(tferSkill, "binds: t/capabilities/c@1.0.0\n", + "binds: t/capabilities/c@1.0.0\ninstructions: from frontmatter\n", 1) + root := writeSource(t, map[string]string{"s.tfer": dual}) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "both the body and frontmatter") { + t.Fatalf("expected dual-instructions error, got %v", err) + } +} + +func TestTferMissingOpeningFenceRejected(t *testing.T) { + root := writeSource(t, map[string]string{"s.tfer": "schemaVersion: 3\nkind: skill\nid: t/skills/s@1.0.0\nbinds: t/capabilities/c@1.0.0\n"}) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "must begin with a '---' frontmatter fence") { + t.Fatalf("expected opening-fence error, got %v", err) + } +} + +func TestTferMissingClosingFenceRejected(t *testing.T) { + root := writeSource(t, map[string]string{"s.tfer": "---\nschemaVersion: 3\nkind: skill\nid: t/skills/s@1.0.0\nbinds: t/capabilities/c@1.0.0\n"}) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "missing its closing '---' frontmatter fence") { + t.Fatalf("expected closing-fence error, got %v", err) + } +} + +func TestTferContextObjectLoads(t *testing.T) { + ctxType := `--- +schemaVersion: 3 +kind: contextType +id: t/context-types/cast@1.0.0 +displayName: Cast of Characters +schema: '{"type":"object","properties":{"role":{"type":"string"}}}' +--- +` + ctxObj := `--- +schemaVersion: 3 +kind: context +id: t/notes/principal@1.0.0 +contextType: t/context-types/cast@1.0.0 +--- +The principal prefers short decision briefs. +` + root := writeSource(t, map[string]string{"cast.tfer": ctxType, "principal.tfer": ctxObj}) + docs, err := Load(root, "") + if err != nil { + t.Fatal(err) + } + ct := docs["t/context-types/cast@1.0.0"] + if ct == nil || ct.Kind != "contextType" { + t.Fatal("contextType not loaded") + } + obj := docs["t/notes/principal@1.0.0"] + if obj == nil { + t.Fatal("context object not loaded") + } + if obj.ContextType != "t/context-types/cast@1.0.0" { + t.Errorf("context object lost its contextType: %q", obj.ContextType) + } + if obj.Content != "The principal prefers short decision briefs.\n" { + t.Errorf("context body did not become content: %q", obj.Content) + } +} + +func TestTferContextRequiresContextType(t *testing.T) { + root := writeSource(t, map[string]string{"n.tfer": "---\nschemaVersion: 3\nkind: context\nid: t/notes/n@1.0.0\n---\nbody\n"}) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "must declare a contextType") { + t.Fatalf("expected missing-contextType error, got %v", err) + } +} + +func TestTferBodyOnBodylessKindRejected(t *testing.T) { + root := writeSource(t, map[string]string{"a.tfer": "---\nschemaVersion: 3\nkind: agent\nid: t/agent@1.0.0\n---\nstray body\n"}) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "has no body field") { + t.Fatalf("expected bodyless-kind error, got %v", err) + } +} + +func TestContextObjectCollectsSchemaFields(t *testing.T) { + src := "schemaVersion: 3\nkind: context\nid: t/notes/n@1.0.0\ncontextType: t/ct/cast@1.0.0\nrole: owner\ntags:\n - a\n" + root := writeSource(t, map[string]string{"n.yaml": src}) + docs, err := Load(root, "") + if err != nil { + t.Fatal(err) + } + d := docs["t/notes/n@1.0.0"] + if d.ContextFields["role"].Scalar != "owner" || d.ContextFields["role"].Kind != "scalar" { + t.Errorf("scalar field 'role' not collected: %v", d.ContextFields) + } + if d.ContextFields["tags"].Kind != "sequence" { + t.Errorf("sequence field 'tags' should be recorded with sequence kind, got %v", d.ContextFields["tags"]) + } +} + +func TestNonContextStillRejectsUnknownFields(t *testing.T) { + // The context-field collection must not weaken strictness for other kinds. + root := writeSource(t, map[string]string{ + "cap.yaml": "schemaVersion: 3\nkind: capability\nid: t/cap/c@1.0.0\nrole: owner\n", + }) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "'role'") { + t.Fatalf("a capability with an unknown field must still error, got %v", err) + } +} + +func TestYamlAndTferInteroperate(t *testing.T) { + root := writeSource(t, map[string]string{ + "agent.yaml": minimalAgent, + "skill.tfer": tferSkill, + }) + docs, err := Load(root, "") + if err != nil { + t.Fatal(err) + } + if docs["t/agent@1.0.0"] == nil || docs["t/skills/s@1.0.0"] == nil { + t.Fatalf("expected both .yaml and .tfer resources, got %d", len(docs)) + } +} diff --git a/go/internal/resource/model.go b/go/internal/resource/model.go index 9d5411f..0501eaf 100644 --- a/go/internal/resource/model.go +++ b/go/internal/resource/model.go @@ -23,13 +23,66 @@ type Document struct { Instructions string InputSchema string OutputSchema string + // ContextType is the id of the contextType a `kind: context` object + // instantiates (ADR-0013). + ContextType string + // Schema is an optional JSON Schema over a `kind: contextType`'s + // frontmatter (ADR-0013). + Schema string + // Content is a `.tfer` markdown body materialized onto a resource: a + // skill's instructions or a context object's content (ADR-0013 format). + Content string + // ContextFields holds a `kind: context` object's schema-typed frontmatter + // fields (those beyond the standard keys). The declaring contextType's schema + // validates their presence and type (ADR-0013). + ContextFields map[string]FieldValue + // RequiresContextTypes are contextType ids a skill needs; the holding + // agent must supply context satisfying each (ADR-0013). + RequiresContextTypes []string + // Context are context-object ids an agent or profile holds by reference + // rather than by path (ADR-0013 reference-by-id). + Context []string + // AllowedContextTypes whitelists the contextType ids a component (and + // anything embedding it) may hold; empty means unrestricted. Intersects + // through embeds — the most restrictive ancestor wins (ADR-0013). + AllowedContextTypes []string + // RequiresTools are tool ids a skill depends on; each must be declared and + // its interface shape-checked (ADR-0017). + RequiresTools []string + // Visibility is "internal" (default) or "exposed" for a capability + // (ADR-0015). Empty means internal. + Visibility string + // Variants holds mode-specific renderings for a multimodal skill + // (ADR-0012): mode name -> variant. A skill declares either Instructions + // or Variants, never both. + Variants map[string]Variant } // SkillBinding attaches a skill implementation (and optionally the capability -// it must satisfy) to an agent or profile. +// it must satisfy) to an agent or profile. Sealed marks the binding as +// non-overridable by embedders; Required marks it mandatory (ADR-0016). type SkillBinding struct { Ref string Capability *string + Sealed bool + Required bool +} + +// FieldValue is a context object's frontmatter field: its structural Kind +// ("scalar", "sequence", or "mapping") and, for scalars, the text value. Kind +// lets the contextType schema check declared field types (ADR-0013). +type FieldValue struct { + Kind string + Scalar string +} + +// Variant is a mode-specific rendering of a multimodal skill (ADR-0012). It +// varies instructions and may narrow requirements; the capability contract +// (schemas) is invariant. +type Variant struct { + Instructions string + RequiresContextTypes []string + RequiresTools []string } // NewDocument returns a Document carrying the spec-defined defaults. diff --git a/go/internal/resource/project.go b/go/internal/resource/project.go new file mode 100644 index 0000000..cddcc39 --- /dev/null +++ b/go/internal/resource/project.go @@ -0,0 +1,49 @@ +package resource + +import ( + "bytes" + "os" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +// Project is the optional source-root manifest (`typeference.yaml`): the +// project's declared identity and publisher. It is the home for settings that +// were otherwise passed on the CLI every build or derived from the folder name +// — analogous to go.mod / Cargo.toml / package.json. +type Project struct { + Name string + Version string + Publisher string +} + +// ProjectManifestFile is the source-root manifest filename. +const ProjectManifestFile = "typeference.yaml" + +// LoadProject reads the project manifest from a source directory. It returns +// (nil, nil) when no manifest is present — the manifest is optional. +func LoadProject(sourceDir string) (*Project, error) { + raw, err := os.ReadFile(filepath.Join(sourceDir, ProjectManifestFile)) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, Errorf("%s: %s", ProjectManifestFile, err) + } + var doc struct { + SchemaVersion int `yaml:"schemaVersion"` + Name string `yaml:"name"` + Version string `yaml:"version"` + Publisher string `yaml:"publisher"` + } + dec := yaml.NewDecoder(bytes.NewReader(raw)) + dec.KnownFields(true) + if err := dec.Decode(&doc); err != nil { + return nil, Errorf("%s: invalid manifest: %s", ProjectManifestFile, err) + } + if doc.SchemaVersion != 1 { + return nil, Errorf("%s: schemaVersion must be 1", ProjectManifestFile) + } + return &Project{Name: doc.Name, Version: doc.Version, Publisher: doc.Publisher}, nil +} diff --git a/go/internal/resource/variants_test.go b/go/internal/resource/variants_test.go new file mode 100644 index 0000000..ef71034 --- /dev/null +++ b/go/internal/resource/variants_test.go @@ -0,0 +1,43 @@ +package resource + +import ( + "strings" + "testing" +) + +func TestVariantsOnlyOnSkills(t *testing.T) { + root := writeSource(t, map[string]string{ + "agent.yaml": minimalAgent + "variants:\n manual:\n instructions: hi\n", + }) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "only skills declare variants") { + t.Fatalf("expected variants-kind error, got %v", err) + } +} + +func TestVariantsAndInstructionsMutuallyExclusive(t *testing.T) { + skill := "schemaVersion: 3\nkind: skill\nid: t/skills/s@1.0.0\nbinds: t/capabilities/c@1.0.0\ninstructions: top\nvariants:\n manual:\n instructions: hi\n" + root := writeSource(t, map[string]string{"s.yaml": skill}) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "either instructions or variants") { + t.Fatalf("expected instructions/variants exclusivity error, got %v", err) + } +} + +func TestVariantRequiresInstructions(t *testing.T) { + skill := "schemaVersion: 3\nkind: skill\nid: t/skills/s@1.0.0\nbinds: t/capabilities/c@1.0.0\nvariants:\n manual:\n instructions: ''\n" + root := writeSource(t, map[string]string{"s.yaml": skill}) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "variant 'manual' must set instructions") { + t.Fatalf("expected empty-variant error, got %v", err) + } +} + +func TestMultimodalTferSkillRejectsBody(t *testing.T) { + skill := "---\nschemaVersion: 3\nkind: skill\nid: t/skills/s@1.0.0\nbinds: t/capabilities/c@1.0.0\nvariants:\n manual:\n instructions: hi\n---\nstray body\n" + root := writeSource(t, map[string]string{"s.tfer": skill}) + _, err := Load(root, "") + if err == nil || !strings.Contains(err.Error(), "multimodal skill has no single body") { + t.Fatalf("expected multimodal-body error, got %v", err) + } +} diff --git a/src/TypeFerence.Cli/Program.cs b/src/TypeFerence.Cli/Program.cs deleted file mode 100644 index 0090d9f..0000000 --- a/src/TypeFerence.Cli/Program.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System.Text.Json; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using ModelContextProtocol.Server; -using TypeFerence.Core; - -return await Entry.RunAsync(args); - -internal static class Entry -{ - public static async Task RunAsync(string[] args) - { - try - { - if (args.Length == 0 || args[0] is "-h" or "--help" or "help") return Help(); - return args[0] switch - { - "validate" => Validate(args), - "build" => Build(args), - "inspect" => Inspect(args), - "diff" => Diff(args), - "serve" => await ServeAsync(args), - "version" or "--version" => Version(), - _ => Fail($"Unknown command: {args[0]}") - }; - } - catch (TypeFerenceException ex) { return Fail(ex.Message); } - catch (Exception ex) { return Fail(ex.ToString()); } - } - - private static int Validate(string[] args) - { - var source = Required(args, 1, "source"); - var agents = new TypeFerenceCompiler().Validate(source, Option(args, "--trust-config")); - Console.WriteLine($"Valid: {agents.Count} agents resolved."); - return 0; - } - - private static int Build(string[] args) - { - var source = Required(args, 1, "source"); - var output = Option(args, "--out") ?? "dist"; - var target = Option(args, "--target") ?? "all"; - var publisherDomain = Option(args, "--publisher-domain"); - var trustConfig = Option(args, "--trust-config"); - var trustSignatures = Option(args, "--trust-signatures"); - var allowUnsignedTrust = args.Contains("--allow-unsigned-trust", StringComparer.Ordinal); - var emitArd = args.Contains("--emit-ard", StringComparer.Ordinal); - if (emitArd && publisherDomain is null) throw new TypeFerenceException("--emit-ard requires --publisher-domain"); - if (!emitArd && publisherDomain is not null) throw new TypeFerenceException("--publisher-domain requires --emit-ard"); - if (!emitArd && trustConfig is not null) throw new TypeFerenceException("--trust-config requires --emit-ard"); - if (!emitArd && trustSignatures is not null) throw new TypeFerenceException("--trust-signatures requires --emit-ard"); - if (!emitArd && allowUnsignedTrust) throw new TypeFerenceException("--allow-unsigned-trust requires --emit-ard"); - var files = new TypeFerenceCompiler().Build( - source, - output, - ParseTargets(target), - emitArd ? new ArdPublicationOptions(publisherDomain!, trustConfig, trustSignatures, allowUnsignedTrust) : null); - Console.WriteLine($"Built {files.Count} files at {Path.GetFullPath(output)}"); - Console.WriteLine($"SHA-256 {TypeFerenceCompiler.HashDirectory(output)}"); - return 0; - } - - private static int Inspect(string[] args) - { - var source = Option(args, "--source") ?? "."; - var id = Required(args, 1, "agent id"); - var agent = new TypeFerenceCompiler().Validate(source).SingleOrDefault(x => x.Id == id) - ?? throw new TypeFerenceException($"Agent not found: {id}"); - Console.WriteLine(JsonSerializer.Serialize(agent, new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase })); - return 0; - } - - private static int Diff(string[] args) - { - var source = Required(args, 1, "source"); - var against = Option(args, "--against") ?? throw new TypeFerenceException("--against is required"); - var temp = Path.Combine(Path.GetTempPath(), "typeference-diff-" + Guid.NewGuid().ToString("N")); - try - { - var publisherDomain = Option(args, "--publisher-domain"); - var trustConfig = Option(args, "--trust-config"); - var trustSignatures = Option(args, "--trust-signatures"); - var allowUnsignedTrust = args.Contains("--allow-unsigned-trust", StringComparer.Ordinal); - var emitArd = args.Contains("--emit-ard", StringComparer.Ordinal); - if (emitArd && publisherDomain is null) throw new TypeFerenceException("--emit-ard requires --publisher-domain"); - if (!emitArd && publisherDomain is not null) throw new TypeFerenceException("--publisher-domain requires --emit-ard"); - if (!emitArd && trustConfig is not null) throw new TypeFerenceException("--trust-config requires --emit-ard"); - if (!emitArd && trustSignatures is not null) throw new TypeFerenceException("--trust-signatures requires --emit-ard"); - if (!emitArd && allowUnsignedTrust) throw new TypeFerenceException("--allow-unsigned-trust requires --emit-ard"); - new TypeFerenceCompiler().Build( - source, - temp, - ParseTargets(Option(args, "--target") ?? "all"), - emitArd ? new ArdPublicationOptions(publisherDomain!, trustConfig, trustSignatures, allowUnsignedTrust) : null); - var result = DiffResult.Compare(against, temp); - if (args.Contains("--json", StringComparer.Ordinal)) Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true })); - else - { - foreach (var x in result.Added) Console.WriteLine($"+ {x}"); - foreach (var x in result.Removed) Console.WriteLine($"- {x}"); - foreach (var x in result.Changed) Console.WriteLine($"~ {x}"); - if (!result.Different) Console.WriteLine("No differences."); - } - return result.Different ? 1 : 0; - } - finally { if (Directory.Exists(temp)) Directory.Delete(temp, true); } - } - - private static async Task ServeAsync(string[] args) - { - var source = Required(args, 1, "source or compiled directory"); - var agents = Directory.EnumerateFiles(source, "*.yaml", SearchOption.AllDirectories).Any() - ? new TypeFerenceCompiler().Validate(source).Where(x => x.Emit).ToArray() - : TypeFerenceCompiler.LoadCompiled(source).Where(x => x.Emit).ToArray(); - var tools = agents.SelectMany(agent => agent.Skills.Select(skill => - { - Func handler = arguments => JsonSerializer.Serialize(TypeFerenceCompiler.Invoke(agent, skill, arguments)); - return McpServerTool.Create(handler, new McpServerToolCreateOptions - { - Name = skill.DispatchName, - Description = skill.Description, - ReadOnly = true, - Destructive = false, - OpenWorld = false - }); - })).ToArray(); - - var builder = Host.CreateApplicationBuilder(); - builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace); - builder.Services.AddMcpServer().WithStdioServerTransport().WithTools(tools); - await builder.Build().RunAsync(); - return 0; - } - - private static CompilationTarget[] ParseTargets(string target) => target.ToLowerInvariant() switch - { - "all" => Enum.GetValues(), - "neutral" => [CompilationTarget.Neutral], - "codex" => [CompilationTarget.Codex], - "copilot" => [CompilationTarget.Copilot], - "cursor" => [CompilationTarget.Cursor], - _ => throw new TypeFerenceException($"Unknown target: {target}") - }; - - private static string Required(string[] args, int index, string name) => args.Length > index ? args[index] : throw new TypeFerenceException($"Missing {name}"); - private static string? Option(string[] args, string name) - { - var i = Array.IndexOf(args, name); - if (i < 0) return null; - if (i + 1 >= args.Length || args[i + 1].StartsWith("--", StringComparison.Ordinal)) - throw new TypeFerenceException($"{name} requires a value"); - return args[i + 1]; - } - private static int Version() - { - var informational = typeof(Entry).Assembly - .GetCustomAttributes(typeof(System.Reflection.AssemblyInformationalVersionAttribute), false) - .OfType() - .FirstOrDefault()?.InformationalVersion ?? "unknown"; - // Deterministic builds may append "+"; the version is the part before it. - Console.WriteLine($"typeference {informational.Split('+')[0]}"); - return 0; - } - - private static int Fail(string message) { Console.Error.WriteLine($"typeference: {message}"); return 2; } - private static int Help() - { - Console.WriteLine(""" -TypeFerence - typed coherence for AI agents - -Commands: - typeference validate [--trust-config path] - typeference build [--target all|neutral|codex|copilot|cursor] [--out dist] - [--emit-ard --publisher-domain example.com] [--trust-config path] - [--trust-signatures signatures.json] - [--allow-unsigned-trust] - typeference inspect [--source path] - typeference diff --against [--target all] - [--emit-ard --publisher-domain example.com] [--trust-config path] - [--trust-signatures signatures.json] [--json] - [--allow-unsigned-trust] - typeference serve - typeference version -"""); - return 0; - } -} diff --git a/src/TypeFerence.Cli/TypeFerence.Cli.csproj b/src/TypeFerence.Cli/TypeFerence.Cli.csproj deleted file mode 100644 index 20e1cbd..0000000 --- a/src/TypeFerence.Cli/TypeFerence.Cli.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - Exe - typeference - TypeFerence compiler, diff tool, and MCP server. - - - - - - - diff --git a/src/TypeFerence.Core/CanonicalOrder.cs b/src/TypeFerence.Core/CanonicalOrder.cs deleted file mode 100644 index a9f73db..0000000 --- a/src/TypeFerence.Core/CanonicalOrder.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Text; - -namespace TypeFerence.Core; - -/// -/// Canonical string ordering per the specification: lexicographic by Unicode -/// code point (equivalently UTF-8 byte order). Differs from -/// (UTF-16 code-unit order) for -/// supplementary-plane code points, which UTF-16 orders below U+E000. -/// -public sealed class CanonicalOrder : IComparer -{ - public static readonly CanonicalOrder Instance = new(); - - private CanonicalOrder() - { - } - - public int Compare(string? x, string? y) - { - if (ReferenceEquals(x, y)) return 0; - if (x is null) return -1; - if (y is null) return 1; - var left = x.EnumerateRunes(); - var right = y.EnumerateRunes(); - while (true) - { - var hasLeft = left.MoveNext(); - var hasRight = right.MoveNext(); - if (!hasLeft || !hasRight) return hasLeft.CompareTo(hasRight); - var compared = left.Current.Value.CompareTo(right.Current.Value); - if (compared != 0) return compared; - } - } -} diff --git a/src/TypeFerence.Core/Compiler.cs b/src/TypeFerence.Core/Compiler.cs deleted file mode 100644 index e255798..0000000 --- a/src/TypeFerence.Core/Compiler.cs +++ /dev/null @@ -1,495 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; - -namespace TypeFerence.Core; - -public enum CompilationTarget { Neutral, Codex, Copilot, Cursor } - -public sealed record ArdPublicationOptions( - string PublisherDomain, - string? TrustConfigurationPath = null, - string? TrustSignaturesPath = null, - bool AllowUnsignedTrust = false); - -public sealed class TypeFerenceCompiler -{ - private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - public IReadOnlyList Validate(string source, string? trustConfigurationPath = null) - { - var trust = TrustConfigurationLoader.Load(source, trustConfigurationPath); - var resources = new ResourceLoader().Load(source, trust?.Path); - return new TypeResolver(resources).ResolveAll(); - } - - public IReadOnlyList Build( - string source, - string output, - IEnumerable targets, - ArdPublicationOptions? ardPublication = null) - { - var trust = TrustConfigurationLoader.Load(source, ardPublication?.TrustConfigurationPath); - var resources = new ResourceLoader().Load(source, trust?.Path); - var agents = new TypeResolver(resources).ResolveAll().Where(x => x.Emit).OrderBy(x => x.Id, StringComparer.Ordinal).ToArray(); - var requestedTargets = targets.Distinct().Order().ToArray(); - if (requestedTargets.Length == 0) throw new TypeFerenceException("At least one compilation target is required"); - var root = Path.GetFullPath(output); - Directory.CreateDirectory(root); - var written = new List(); - foreach (var target in requestedTargets) - { - var targetRoot = Path.Combine(root, target.ToString().ToLowerInvariant()); - if (Directory.Exists(targetRoot)) Directory.Delete(targetRoot, true); - Directory.CreateDirectory(targetRoot); - foreach (var agent in agents) WriteTarget(target, targetRoot, agent, written); - } - if (ardPublication is not null) - { - var ardRoot = Path.Combine(root, "ard"); - if (Directory.Exists(ardRoot)) Directory.Delete(ardRoot, true); - Directory.CreateDirectory(ardRoot); - if (ardPublication.TrustSignaturesPath is not null) - { - var sourceRoot = Path.GetFullPath(source).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; - var signaturePath = Path.GetFullPath(ardPublication.TrustSignaturesPath); - if (signaturePath.StartsWith(sourceRoot, StringComparison.OrdinalIgnoreCase)) - throw new TypeFerenceException("Trust signatures file must be outside the source root to avoid a digest/signature cycle"); - } - var signatures = ardPublication.TrustSignaturesPath is null - ? new SortedDictionary(StringComparer.Ordinal) - : TrustConfigurationLoader.LoadSignatures(ardPublication.TrustSignaturesPath); - if (signatures.Count > 0 && trust is null) - throw new TypeFerenceException("--trust-signatures requires a trust configuration"); - if (ardPublication.AllowUnsignedTrust && trust is null) - throw new TypeFerenceException("--allow-unsigned-trust requires a trust configuration"); - WriteArdCatalog(ardRoot, source, root, agents, requestedTargets, ardPublication.PublisherDomain, - trust?.Configuration, signatures, ardPublication.AllowUnsignedTrust, written); - } - return written.Order(StringComparer.Ordinal).ToArray(); - } - - public static InvocationPackage Invoke(ResolvedAgent agent, ResolvedSkill skill, JsonElement arguments) - { - ValidateArguments(skill.InputSchema, arguments); - return new( - agent.Id, - skill.ImplementationId, - skill.DispatchName, - JsonSerializer.Deserialize(arguments.GetRawText()) ?? new { }, - skill.Instructions, - skill.ContextFiles, - new SortedDictionary(StringComparer.Ordinal) - { - ["codex"] = ".agents/skills", - ["copilot"] = ".github/agents", - ["cursor"] = ".cursor/rules" - }, - skill.Provenance); - } - - public static IReadOnlyList LoadCompiled(string directory) - { - var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - var agents = Directory.EnumerateFiles(directory, "bundle.json", SearchOption.AllDirectories) - .Order(StringComparer.Ordinal) - .Select(x => JsonSerializer.Deserialize(File.ReadAllText(x), options) - ?? throw new TypeFerenceException($"Invalid compiled bundle: {x}")) - .ToArray(); - if (agents.Length == 0) throw new TypeFerenceException($"No compiled bundle.json files found under {directory}"); - return agents; - } - - public static string HashDirectory(string directory) - { - using var sha = SHA256.Create(); - var payload = new StringBuilder(); - foreach (var file in EnumerateRelativeFiles(directory)) - payload.Append(file.Relative).Append('\0').Append(File.ReadAllText(file.Full).Replace("\r\n", "\n")).Append('\0'); - return Convert.ToHexString(sha.ComputeHash(Encoding.UTF8.GetBytes(payload.ToString()))).ToLowerInvariant(); - } - - // Spec ("Artifact digest algorithm"): files are ordered by their - // forward-slash relative path in canonical code point order. Sorting - // platform-native paths would make the order (and therefore the digest) - // separator-dependent. - private static (string Full, string Relative)[] EnumerateRelativeFiles(string root) => - Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories) - .Select(x => (Full: x, Relative: Path.GetRelativePath(root, x).Replace('\\', '/'))) - .OrderBy(x => x.Relative, CanonicalOrder.Instance) - .ToArray(); - - private static void WriteTarget(CompilationTarget target, string root, ResolvedAgent agent, List written) - { - var slug = Slug(agent.Id); - switch (target) - { - case CompilationTarget.Neutral: - Write(Path.Combine(root, slug, "AGENTS.md"), RenderInstructions(agent), written); - Write(Path.Combine(root, slug, "bundle.json"), JsonSerializer.Serialize(agent, JsonOptions) + "\n", written); - Write(Path.Combine(root, slug, "provenance.json"), JsonSerializer.Serialize(agent.Provenance, JsonOptions) + "\n", written); - foreach (var skill in agent.Skills) - Write(Path.Combine(root, slug, "skills", SkillSlug(skill), "SKILL.md"), RenderSkill(skill), written); - break; - case CompilationTarget.Codex: - Write(Path.Combine(root, slug, "AGENTS.md"), RenderInstructions(agent), written); - foreach (var skill in agent.Skills) - Write(Path.Combine(root, slug, ".agents", "skills", SkillSlug(skill), "SKILL.md"), RenderSkill(skill), written); - Write(Path.Combine(root, slug, ".typeference", "bundle.json"), JsonSerializer.Serialize(agent, JsonOptions) + "\n", written); - Write(Path.Combine(root, slug, ".codex", "config.toml"), "[mcp_servers.typeference]\ncommand = \"typeference\"\nargs = [\"serve\", \".typeference\"]\n", written); - break; - case CompilationTarget.Copilot: - Write(Path.Combine(root, slug, ".github", "copilot-instructions.md"), RenderInstructions(agent), written); - Write(Path.Combine(root, slug, ".github", "agents", slug + ".agent.md"), $"---\nname: {slug}\ndescription: {EscapeYaml(agent.Description)}\n---\n\n{RenderInstructions(agent)}", written); - Write(Path.Combine(root, slug, ".typeference", "bundle.json"), JsonSerializer.Serialize(agent, JsonOptions) + "\n", written); - break; - case CompilationTarget.Cursor: - Write(Path.Combine(root, slug, "AGENTS.md"), RenderInstructions(agent), written); - Write(Path.Combine(root, slug, ".cursor", "rules", slug + ".mdc"), $"---\ndescription: {EscapeYaml(agent.Description)}\nglobs:\nalwaysApply: true\n---\n\n{RenderInstructions(agent)}", written); - Write(Path.Combine(root, slug, ".typeference", "bundle.json"), JsonSerializer.Serialize(agent, JsonOptions) + "\n", written); - break; - } - } - - private static void WriteArdCatalog( - string ardRoot, - string source, - string outputRoot, - IReadOnlyList agents, - IReadOnlyList targets, - string publisherDomain, - TrustConfiguration? trustConfiguration, - IReadOnlyDictionary signatures, - bool allowUnsignedTrust, - List written) - { - if (!System.Text.RegularExpressions.Regex.IsMatch( - publisherDomain, - "^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$", - System.Text.RegularExpressions.RegexOptions.CultureInvariant)) - throw new TypeFerenceException($"Invalid ARD publisher domain: {publisherDomain}"); - - var sourceName = UrnSegment(Path.GetFileName(Path.GetFullPath(source).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); - var sourceIdentifier = $"urn:air:{publisherDomain}:typeference:source:{sourceName}"; - var sourceDigest = "sha256:" + HashDirectory(source); - var sourceEntry = new - { - identifier = sourceIdentifier, - displayName = $"TypeFerence source package: {sourceName}", - type = "application/vnd.typeference.source-package+json", - description = "Canonical typed source package for validation, audit, and reproducible compilation.", - version = "1.0.0", - data = new - { - schemaVersion = 1, - digest = sourceDigest, - files = PackageFiles(source) - }, - metadata = new SortedDictionary(StringComparer.Ordinal) - { - ["generatedBy"] = "TypeFerence", - ["role"] = "canonical-source" - } - }; - var entries = new List(); - var signedIdentifiers = new HashSet(StringComparer.Ordinal); - if (trustConfiguration?.Source is null) entries.Add(sourceEntry); - else - { - TrustConfigurationLoader.ValidateIdentityForPublisher( - trustConfiguration.Source.Identity, - trustConfiguration.Source.IdentityType, - publisherDomain, - "source.identity"); - var manifest = BuildTrustManifest( - trustConfiguration.Source, - trustConfiguration.Source.Identity, - [], - sourceDigest, - signatures.GetValueOrDefault(sourceIdentifier), - sourceIdentifier, - allowUnsignedTrust); - if (signatures.ContainsKey(sourceIdentifier)) signedIdentifiers.Add(sourceIdentifier); - entries.Add(new - { - sourceEntry.identifier, - sourceEntry.displayName, - sourceEntry.type, - sourceEntry.description, - sourceEntry.version, - sourceEntry.data, - sourceEntry.metadata, - trustManifest = manifest - }); - } - - foreach (var target in targets) - foreach (var agent in agents) - { - var targetName = target.ToString().ToLowerInvariant(); - var agentRoot = Path.Combine(outputRoot, targetName, Slug(agent.Id)); - var identifier = $"urn:air:{publisherDomain}:typeference:{targetName}:{Slug(agent.Id)}"; - var targetEntry = new - { - identifier, - displayName = $"{agent.DisplayName} ({targetName})", - type = "application/vnd.typeference.target-bundle+json", - description = $"Precompiled {targetName} artifact bundle. {agent.Description}", - capabilities = agent.Skills.Select(x => x.DispatchName).Order(StringComparer.Ordinal).ToArray(), - version = agent.Id.Split('@').Last(), - data = new - { - schemaVersion = 1, - target = targetName, - agentId = agent.Id, - files = PackageFiles(agentRoot) - }, - metadata = new SortedDictionary(StringComparer.Ordinal) - { - ["generatedBy"] = "TypeFerence", - ["sourceIdentifier"] = sourceIdentifier, - ["sourceDigest"] = sourceDigest, - ["target"] = targetName - }, - trustManifest = new - { - identity = $"https://{publisherDomain}", - identityType = "https", - provenance = new[] - { - new { relation = "derivedFrom", sourceId = sourceIdentifier, sourceDigest } - } - } - }; - if (trustConfiguration?.Bundles is null) entries.Add(targetEntry); - else - { - var version = agent.Id.Split('@').Last(); - var identity = TrustConfigurationLoader.ExpandIdentity( - trustConfiguration.Bundles.IdentityTemplate, - publisherDomain, - targetName, - Slug(agent.Id), - version); - TrustConfigurationLoader.ValidateIdentityForPublisher( - identity, - trustConfiguration.Bundles.IdentityType, - publisherDomain, - "bundles.identityTemplate"); - var compilerProvenance = new[] - { - new TrustProvenanceLink { Relation = "derivedFrom", SourceId = sourceIdentifier, SourceDigest = sourceDigest } - }; - var artifactDigest = "sha256:" + HashDirectory(agentRoot); - var manifest = BuildTrustManifest( - trustConfiguration.Bundles, - identity, - compilerProvenance, - artifactDigest, - signatures.GetValueOrDefault(identifier), - identifier, - allowUnsignedTrust); - if (signatures.ContainsKey(identifier)) signedIdentifiers.Add(identifier); - entries.Add(new - { - targetEntry.identifier, - targetEntry.displayName, - targetEntry.type, - targetEntry.description, - targetEntry.capabilities, - targetEntry.version, - targetEntry.data, - targetEntry.metadata, - trustManifest = manifest - }); - } - } - var unknownSignature = signatures.Keys.FirstOrDefault(x => !signedIdentifiers.Contains(x)); - if (unknownSignature is not null) - throw new TypeFerenceException($"Trust signature identifier does not match a configured catalog entry: {unknownSignature}"); - var catalog = new - { - specVersion = "1.0", - host = new { displayName = publisherDomain, identifier = publisherDomain }, - entries = entries.ToArray() - }; - Write(Path.Combine(ardRoot, "ai-catalog.json"), JsonSerializer.Serialize(catalog, JsonOptions) + "\n", written); - } - - private static SortedDictionary BuildTrustManifest( - TrustProfile profile, - string identity, - IEnumerable compilerProvenance, - string artifactDigest, - string? signature, - string catalogIdentifier, - bool allowUnsignedTrust) - { - if (profile.SignatureIntent?.Required == true && signature is null && !allowUnsignedTrust) - throw new TypeFerenceException($"Trust signature is required for catalog entry: {catalogIdentifier}"); - var metadata = TrustConfigurationLoader.CanonicalMetadata(profile.Metadata); - var digestKey = TrustConfigurationLoader.TypeFerenceMetadataPrefix + ".artifactDigest"; - var intentKey = TrustConfigurationLoader.TypeFerenceMetadataPrefix + ".signatureIntent"; - if (metadata.ContainsKey(digestKey) || metadata.ContainsKey(intentKey)) - throw new TypeFerenceException($"Trust metadata cannot override TypeFerence-managed keys for catalog entry: {catalogIdentifier}"); - metadata[digestKey] = new SortedDictionary(StringComparer.Ordinal) - { - ["digest"] = artifactDigest, - ["scheme"] = "typeference-directory-v1" - }; - if (profile.SignatureIntent is not null) - { - var intent = new SortedDictionary(StringComparer.Ordinal) - { - ["required"] = profile.SignatureIntent.Required, - ["status"] = "external" - }; - if (!string.IsNullOrWhiteSpace(profile.SignatureIntent.Algorithm)) intent["algorithm"] = profile.SignatureIntent.Algorithm; - if (!string.IsNullOrWhiteSpace(profile.SignatureIntent.KeyRef)) intent["keyRef"] = profile.SignatureIntent.KeyRef; - metadata[intentKey] = intent; - } - - var manifest = new SortedDictionary(StringComparer.Ordinal) - { - ["identity"] = identity, - ["metadata"] = metadata - }; - if (!string.IsNullOrWhiteSpace(profile.IdentityType)) manifest["identityType"] = profile.IdentityType; - if (profile.TrustSchema is not null) manifest["trustSchema"] = TrustSchemaObject(profile.TrustSchema); - if (profile.Attestations.Count > 0) manifest["attestations"] = profile.Attestations.Select(AttestationObject).ToArray(); - var provenance = compilerProvenance.Concat(profile.Provenance).Select(ProvenanceObject).ToArray(); - if (provenance.Length > 0) manifest["provenance"] = provenance; - if (signature is not null) manifest["signature"] = signature; - return manifest; - } - - private static SortedDictionary TrustSchemaObject(TrustSchema value) - { - var result = new SortedDictionary(StringComparer.Ordinal) - { - ["identifier"] = value.Identifier, - ["version"] = value.Version - }; - if (value.GovernanceUri is not null) result["governanceUri"] = value.GovernanceUri; - if (value.VerificationMethods.Count > 0) result["verificationMethods"] = value.VerificationMethods.ToArray(); - return result; - } - - private static SortedDictionary AttestationObject(TrustAttestation value) - { - var result = new SortedDictionary(StringComparer.Ordinal) - { - ["type"] = value.Type, - ["uri"] = value.Uri - }; - if (value.Digest is not null) result["digest"] = value.Digest; - if (value.Size is not null) result["size"] = value.Size; - if (value.Description is not null) result["description"] = value.Description; - return result; - } - - private static SortedDictionary ProvenanceObject(TrustProvenanceLink value) - { - var result = new SortedDictionary(StringComparer.Ordinal) - { - ["relation"] = value.Relation, - ["sourceId"] = value.SourceId - }; - if (value.SourceDigest is not null) result["sourceDigest"] = value.SourceDigest; - if (value.RegistryUri is not null) result["registryUri"] = value.RegistryUri; - if (value.StatementUri is not null) result["statementUri"] = value.StatementUri; - if (value.SignatureRef is not null) result["signatureRef"] = value.SignatureRef; - return result; - } - - private static object[] PackageFiles(string root) => EnumerateRelativeFiles(root) - .Select(file => (object)new - { - path = file.Relative, - mediaType = MediaType(file.Full), - content = File.ReadAllText(file.Full).Replace("\r\n", "\n") - }) - .ToArray(); - - private static string MediaType(string path) => Path.GetExtension(path).ToLowerInvariant() switch - { - ".json" => "application/json", - ".toml" => "application/toml", - ".yaml" or ".yml" => "application/yaml", - ".md" or ".mdc" => "text/markdown", - _ => "text/plain" - }; - - private static string UrnSegment(string value) - { - var segment = System.Text.RegularExpressions.Regex.Replace(value.ToLowerInvariant(), "[^a-z0-9-]+", "-").Trim('-'); - return string.IsNullOrEmpty(segment) ? "package" : segment; - } - - private static string RenderInstructions(ResolvedAgent agent) - { - var b = new StringBuilder($"# {agent.DisplayName}\n\n{agent.Description}\n\n"); - if (agent.WorkingNorms.Count > 0) - { - b.Append("## Working norms\n\n"); - foreach (var norm in agent.WorkingNorms) b.Append("- ").Append(norm).Append('\n'); - b.Append('\n'); - } - if (agent.Slots.Count > 0) - { - b.Append("## Context slots\n\n"); - foreach (var slot in agent.Slots) b.Append("- `").Append(slot.Key).Append("`: `").Append(slot.Value).Append("`\n"); - b.Append('\n'); - } - b.Append("## Available skills\n\n"); - foreach (var skill in agent.Skills) b.Append("- `").Append(skill.DispatchName).Append("`: ").Append(skill.Description).Append('\n'); - return b.Append('\n').ToString(); - } - - private static string RenderSkill(ResolvedSkill skill) => $"---\nname: {SkillSlug(skill)}\ndescription: {EscapeYaml(skill.Description)}\n---\n\n{skill.Instructions.Trim()}\n\n## Context loaded on invocation\n\n{string.Join("\n", skill.ContextFiles.Select(x => $"- `{x}`"))}\n"; - private static string SkillSlug(ResolvedSkill skill) => skill.CapabilityId.Split('/').Last().Split('@')[0]; - private static string Slug(string id) => id.Split('/').Last().Split('@')[0]; - private static string EscapeYaml(string value) => "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; - - private static void Write(string path, string content, List written) - { - Directory.CreateDirectory(Path.GetDirectoryName(path)!); - File.WriteAllText(path, content.Replace("\r\n", "\n"), new UTF8Encoding(false)); - written.Add(path); - } - - private static void ValidateArguments(string schemaJson, JsonElement arguments) - { - if (arguments.ValueKind != JsonValueKind.Object) throw new TypeFerenceException("Skill arguments must be a JSON object"); - using var schema = JsonDocument.Parse(schemaJson); - var root = schema.RootElement; - var names = arguments.EnumerateObject().Select(x => x.Name).ToHashSet(StringComparer.Ordinal); - if (root.TryGetProperty("required", out var required)) - foreach (var item in required.EnumerateArray()) - if (!names.Contains(item.GetString()!)) throw new TypeFerenceException($"Missing required skill argument: {item.GetString()}"); - if (root.TryGetProperty("additionalProperties", out var additional) && additional.ValueKind == JsonValueKind.False && - root.TryGetProperty("properties", out var properties)) - { - var allowed = properties.EnumerateObject().Select(x => x.Name).ToHashSet(StringComparer.Ordinal); - var unknown = names.FirstOrDefault(x => !allowed.Contains(x)); - if (unknown is not null) throw new TypeFerenceException($"Unknown skill argument: {unknown}"); - } - } -} - -public sealed record DiffResult(bool Different, IReadOnlyList Added, IReadOnlyList Removed, IReadOnlyList Changed) -{ - public static DiffResult Compare(string expected, string actual) - { - var left = Files(expected); - var right = Files(actual); - var added = right.Keys.Except(left.Keys, StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(); - var removed = left.Keys.Except(right.Keys, StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(); - var changed = left.Keys.Intersect(right.Keys, StringComparer.Ordinal).Where(x => left[x] != right[x]).Order(StringComparer.Ordinal).ToArray(); - return new(added.Length + removed.Length + changed.Length > 0, added, removed, changed); - } - - private static SortedDictionary Files(string root) => Directory.Exists(root) - ? new(Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories).ToDictionary(x => Path.GetRelativePath(root, x).Replace('\\', '/'), File.ReadAllText, StringComparer.Ordinal), StringComparer.Ordinal) - : new(StringComparer.Ordinal); -} diff --git a/src/TypeFerence.Core/Models.cs b/src/TypeFerence.Core/Models.cs deleted file mode 100644 index 2a3c0a3..0000000 --- a/src/TypeFerence.Core/Models.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TypeFerence.Core; - -public sealed class ResourceDocument -{ - public int SchemaVersion { get; set; } - public string Kind { get; set; } = ""; - public string Id { get; set; } = ""; - public string DisplayName { get; set; } = ""; - public string Description { get; set; } = ""; - public string Binds { get; set; } = ""; - public bool Emit { get; set; } = true; - public List Embeds { get; set; } = []; - public List RequiresSlots { get; set; } = []; - public List RequiresCapabilities { get; set; } = []; - public SortedDictionary Slots { get; set; } = new(StringComparer.Ordinal); - public List WorkingNorms { get; set; } = []; - public List ContextFiles { get; set; } = []; - public List Skills { get; set; } = []; - public string Instructions { get; set; } = ""; - public string InputSchema { get; set; } = "{\"type\":\"object\",\"additionalProperties\":false}"; - public string OutputSchema { get; set; } = "{\"type\":\"object\"}"; -} - -public sealed class SkillBinding -{ - public string Ref { get; set; } = ""; - public string? Capability { get; set; } -} - -public sealed record ProvenanceEntry(string Field, string Source); - -public sealed class ResolvedSkill -{ - public string DispatchName { get; init; } = ""; - public string CapabilityId { get; init; } = ""; - public string ImplementationId { get; init; } = ""; - public string Description { get; init; } = ""; - public string Instructions { get; init; } = ""; - public string InputSchema { get; init; } = ""; - public string OutputSchema { get; init; } = ""; - public IReadOnlyList ContextFiles { get; init; } = []; - public IReadOnlyList Provenance { get; init; } = []; -} - -public sealed class ResolvedAgent -{ - public string Id { get; init; } = ""; - public string DisplayName { get; init; } = ""; - public string Description { get; init; } = ""; - public bool Emit { get; init; } = true; - public IReadOnlyList Embeds { get; init; } = []; - public IReadOnlyList Satisfies { get; init; } = []; - public IReadOnlyDictionary Slots { get; init; } = new SortedDictionary(); - public IReadOnlyList WorkingNorms { get; init; } = []; - public IReadOnlyList ContextFiles { get; init; } = []; - public IReadOnlyList Skills { get; init; } = []; - public IReadOnlyList Provenance { get; init; } = []; -} - -public sealed record InvocationPackage( - string AgentId, - string SkillId, - string DispatchName, - object Arguments, - string Instructions, - IReadOnlyList ContextReferences, - IReadOnlyDictionary TargetHints, - IReadOnlyList Provenance); - -public sealed class TypeFerenceException(string message) : Exception(message); diff --git a/src/TypeFerence.Core/ResourceLoader.cs b/src/TypeFerence.Core/ResourceLoader.cs deleted file mode 100644 index d73b9c7..0000000 --- a/src/TypeFerence.Core/ResourceLoader.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Text.Json; -using System.Text.RegularExpressions; -using YamlDotNet.Core; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace TypeFerence.Core; - -public sealed class ResourceLoader -{ - private readonly IDeserializer _yaml = new DeserializerBuilder() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - - private static readonly Regex ResourceId = new( - "^[a-z0-9][a-z0-9.-]*(?:/[a-z0-9][a-z0-9.-]*)+@[0-9]+\\.[0-9]+\\.[0-9]+(?:-[0-9A-Za-z.-]+)?$", - RegexOptions.CultureInvariant); - - // Spec ("Canonical text and ordering"): slot names are a canonical key - // space and must be ASCII so every conforming implementation orders them - // identically. - private static readonly Regex SlotName = new("^[A-Za-z0-9][A-Za-z0-9._-]*$", RegexOptions.CultureInvariant); - - public IReadOnlyDictionary Load(string sourceDirectory, string? trustConfigurationPath = null) - { - var root = Path.GetFullPath(sourceDirectory); - if (!Directory.Exists(root)) throw new TypeFerenceException($"Source directory not found: {root}"); - var result = new SortedDictionary(StringComparer.Ordinal); - var excludedTrustFiles = new HashSet(StringComparer.OrdinalIgnoreCase) - { - Path.Combine(root, TrustConfigurationLoader.DefaultFileName) - }; - if (trustConfigurationPath is not null) excludedTrustFiles.Add(Path.GetFullPath(trustConfigurationPath)); - foreach (var file in Directory.EnumerateFiles(root, "*.yaml", SearchOption.AllDirectories) - .Where(x => x.EndsWith(".yaml", StringComparison.Ordinal)) // pin: case-insensitive file systems must not admit .YAML - .Where(x => !excludedTrustFiles.Contains(Path.GetFullPath(x))) - .Order(StringComparer.Ordinal)) - { - ResourceDocument resource; - try - { - resource = _yaml.Deserialize(File.ReadAllText(file)) - ?? throw new TypeFerenceException($"Empty resource: {file}"); - } - catch (YamlException ex) - { - throw new TypeFerenceException($"{file}: invalid YAML resource: {ex.Message}"); - } - ValidateShape(resource, file, root); - if (!result.TryAdd(resource.Id, resource)) throw new TypeFerenceException($"Duplicate resource id: {resource.Id}"); - } - if (result.Count == 0) throw new TypeFerenceException($"No YAML resources found under {root}"); - return result; - } - - private static void ValidateShape(ResourceDocument resource, string file, string root) - { - if (resource.SchemaVersion != 3) throw new TypeFerenceException($"{file}: schemaVersion must be 3"); - if (resource.Kind is not ("agent" or "profile" or "interface" or "capability" or "skill")) throw new TypeFerenceException($"{file}: unknown kind '{resource.Kind}'"); - if (!ResourceId.IsMatch(resource.Id)) - throw new TypeFerenceException($"{file}: id must use lowercase namespace/name@semantic-version"); - if (resource.Kind is "capability" or "skill" && resource.Embeds.Count != 0) throw new TypeFerenceException($"{file}: {resource.Kind}s cannot embed resources"); - if (resource.Kind == "skill" && string.IsNullOrWhiteSpace(resource.Binds)) throw new TypeFerenceException($"{file}: skills must bind a capability"); - if (resource.Kind == "skill" && !ResourceId.IsMatch(resource.Binds)) throw new TypeFerenceException($"{file}: binds must reference a capability id"); - if (resource.Kind != "skill" && !string.IsNullOrWhiteSpace(resource.Binds)) throw new TypeFerenceException($"{file}: only skills can bind capabilities"); - foreach (var name in resource.Slots.Keys.Concat(resource.RequiresSlots)) - if (!SlotName.IsMatch(name)) - throw new TypeFerenceException($"{file}: slot name '{name}' must be an ASCII identifier matching [A-Za-z0-9][A-Za-z0-9._-]*"); - foreach (var relative in resource.ContextFiles.Concat(resource.Slots.Values)) - { - var full = Path.GetFullPath(Path.Combine(root, relative.Replace('/', Path.DirectorySeparatorChar))); - if (!full.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) - throw new TypeFerenceException($"{file}: path escapes source root: {relative}"); - if (!File.Exists(full)) throw new TypeFerenceException($"{file}: referenced file does not exist: {relative}"); - } - ValidateJson(resource.InputSchema, file, "inputSchema"); - ValidateJson(resource.OutputSchema, file, "outputSchema"); - } - - private static void ValidateJson(string value, string file, string field) - { - try { using var _ = JsonDocument.Parse(value); } - catch (JsonException ex) { throw new TypeFerenceException($"{file}: invalid {field}: {ex.Message}"); } - } -} diff --git a/src/TypeFerence.Core/TrustModels.cs b/src/TypeFerence.Core/TrustModels.cs deleted file mode 100644 index 16ef171..0000000 --- a/src/TypeFerence.Core/TrustModels.cs +++ /dev/null @@ -1,326 +0,0 @@ -using System.Collections; -using System.Text.Json; -using System.Text.RegularExpressions; -using YamlDotNet.Core; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace TypeFerence.Core; - -public sealed class TrustConfiguration -{ - public int SchemaVersion { get; set; } - public SourceTrustProfile? Source { get; set; } - public BundleTrustProfile? Bundles { get; set; } -} - -public class TrustProfile -{ - public string IdentityType { get; set; } = ""; - public TrustSchema? TrustSchema { get; set; } - public List Attestations { get; set; } = []; - public List Provenance { get; set; } = []; - public SortedDictionary Metadata { get; set; } = new(StringComparer.Ordinal); - public SignatureIntent? SignatureIntent { get; set; } -} - -public sealed class SourceTrustProfile : TrustProfile -{ - public string Identity { get; set; } = ""; -} - -public sealed class BundleTrustProfile : TrustProfile -{ - public string IdentityTemplate { get; set; } = ""; -} - -public sealed class TrustSchema -{ - public string Identifier { get; set; } = ""; - public string Version { get; set; } = ""; - public string? GovernanceUri { get; set; } - public List VerificationMethods { get; set; } = []; -} - -public sealed class TrustAttestation -{ - public string Type { get; set; } = ""; - public string Uri { get; set; } = ""; - public string? Digest { get; set; } - public long? Size { get; set; } - public string? Description { get; set; } -} - -public sealed class TrustProvenanceLink -{ - public string Relation { get; set; } = ""; - public string SourceId { get; set; } = ""; - public string? SourceDigest { get; set; } - public string? RegistryUri { get; set; } - public string? StatementUri { get; set; } - public string? SignatureRef { get; set; } -} - -public sealed class SignatureIntent -{ - public string? Algorithm { get; set; } - public string? KeyRef { get; set; } - public bool Required { get; set; } -} - -internal sealed record LoadedTrustConfiguration(TrustConfiguration Configuration, string Path); - -internal static partial class TrustConfigurationLoader -{ - internal const string DefaultFileName = "typeference.trust.yaml"; - internal const string TypeFerenceMetadataPrefix = "com.github.buchk.typeference"; - - private static readonly IDeserializer Yaml = new DeserializerBuilder() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - - private static readonly HashSet KnownIdentityTypes = new(StringComparer.Ordinal) - { - "did", "dns", "https", "spiffe" - }; - - internal static LoadedTrustConfiguration? Load(string sourceDirectory, string? configuredPath = null) - { - var root = Path.GetFullPath(sourceDirectory); - var path = configuredPath is null - ? Path.Combine(root, DefaultFileName) - : Path.GetFullPath(configuredPath, Directory.GetCurrentDirectory()); - if (configuredPath is null && !File.Exists(path)) return null; - EnsureBeneathRoot(root, path); - if (!File.Exists(path)) throw new TypeFerenceException($"Trust configuration not found: {path}"); - - TrustConfiguration configuration; - try - { - configuration = Yaml.Deserialize(File.ReadAllText(path)) - ?? throw new TypeFerenceException($"Empty trust configuration: {path}"); - } - catch (YamlException ex) - { - throw new TypeFerenceException($"{path}: invalid trust configuration: {ex.Message}"); - } - - NormalizeMetadata(configuration); - Validate(configuration, path); - return new(configuration, path); - } - - internal static IReadOnlyDictionary LoadSignatures(string path) - { - var full = Path.GetFullPath(path); - if (!File.Exists(full)) throw new TypeFerenceException($"Trust signatures file not found: {full}"); - try - { - using var document = JsonDocument.Parse(File.ReadAllText(full)); - if (document.RootElement.ValueKind != JsonValueKind.Object) - throw new TypeFerenceException("Trust signatures file must be a JSON object keyed by catalog identifier"); - var result = new SortedDictionary(StringComparer.Ordinal); - foreach (var property in document.RootElement.EnumerateObject()) - { - if (property.Value.ValueKind != JsonValueKind.String) - throw new TypeFerenceException($"Trust signature for {property.Name} must be a string"); - var signature = property.Value.GetString()!; - ValidateDetachedJws(signature, property.Name); - if (!result.TryAdd(property.Name, signature)) - throw new TypeFerenceException($"Duplicate trust signature identifier: {property.Name}"); - } - return result; - } - catch (JsonException ex) - { - throw new TypeFerenceException($"{full}: invalid trust signatures JSON: {ex.Message}"); - } - } - - internal static void ValidateIdentityForPublisher(string identity, string identityType, string publisherDomain, string field) - { - ValidateIdentity(identity, identityType, field); - var uri = new Uri(identity); - string? domain = null; - if (identity.StartsWith("did:web:", StringComparison.Ordinal)) - domain = Uri.UnescapeDataString(identity[8..].Split(':', '/', '?', '#')[0]).Split(':')[0]; - else if (string.Equals(uri.Scheme, "dns", StringComparison.Ordinal)) - domain = identity[(identity.IndexOf(':') + 1)..].TrimStart('/').Split('/', '?', '#')[0]; - else if (!string.IsNullOrWhiteSpace(uri.Host)) domain = uri.IdnHost; - if (string.IsNullOrWhiteSpace(domain)) - throw new TypeFerenceException($"{field} does not expose an authority or trust domain that can align with ARD publisher domain '{publisherDomain}'"); - if (!string.Equals(domain, publisherDomain, StringComparison.OrdinalIgnoreCase)) - throw new TypeFerenceException($"{field} domain '{domain}' does not align with ARD publisher domain '{publisherDomain}'"); - } - - internal static SortedDictionary CanonicalMetadata(IEnumerable> metadata) => - new(metadata.ToDictionary(x => CheckedMetadataKey(x.Key), x => CanonicalValue(x.Value), StringComparer.Ordinal), StringComparer.Ordinal); - - private static string CheckedMetadataKey(string key) => MetadataKey().IsMatch(key) - ? key - : throw new TypeFerenceException("Trust metadata keys must be ASCII identifiers matching [A-Za-z0-9][A-Za-z0-9._-]*"); - - private static void Validate(TrustConfiguration configuration, string file) - { - if (configuration.SchemaVersion != 1) throw new TypeFerenceException($"{file}: schemaVersion must be 1"); - if (configuration.Source is null && configuration.Bundles is null) - throw new TypeFerenceException($"{file}: at least one of source or bundles is required"); - if (configuration.Source is not null) - { - if (string.IsNullOrWhiteSpace(configuration.Source.Identity)) - throw new TypeFerenceException($"{file}: source.identity is required"); - ValidateIdentity(configuration.Source.Identity, configuration.Source.IdentityType, "source.identity"); - ValidateProfile(configuration.Source, "source"); - } - if (configuration.Bundles is not null) - { - var template = configuration.Bundles.IdentityTemplate; - if (string.IsNullOrWhiteSpace(template)) throw new TypeFerenceException($"{file}: bundles.identityTemplate is required"); - var placeholders = Placeholder().Matches(template).Select(x => x.Groups[1].Value).ToArray(); - var allowed = new HashSet(["publisher", "target", "agent", "version"], StringComparer.Ordinal); - var unknown = placeholders.FirstOrDefault(x => !allowed.Contains(x)); - if (unknown is not null) throw new TypeFerenceException($"{file}: unknown identity template placeholder '{{{unknown}}}'"); - if (!placeholders.Contains("agent", StringComparer.Ordinal) || !placeholders.Contains("target", StringComparer.Ordinal)) - throw new TypeFerenceException($"{file}: bundles.identityTemplate must contain {{agent}} and {{target}}"); - var sample = ExpandIdentity(template, "example.com", "neutral", "agent", "1.0.0"); - ValidateIdentity(sample, configuration.Bundles.IdentityType, "bundles.identityTemplate"); - ValidateProfile(configuration.Bundles, "bundles"); - } - } - - internal static string ExpandIdentity(string template, string publisher, string target, string agent, string version) => template - .Replace("{publisher}", publisher, StringComparison.Ordinal) - .Replace("{target}", target, StringComparison.Ordinal) - .Replace("{agent}", agent, StringComparison.Ordinal) - .Replace("{version}", version, StringComparison.Ordinal); - - private static void ValidateProfile(TrustProfile profile, string field) - { - if (profile.IdentityType.Length > 0 && string.IsNullOrWhiteSpace(profile.IdentityType)) - throw new TypeFerenceException($"{field}.identityType cannot be whitespace"); - if (profile.TrustSchema is not null) - { - Required(profile.TrustSchema.Identifier, $"{field}.trustSchema.identifier"); - Required(profile.TrustSchema.Version, $"{field}.trustSchema.version"); - OptionalUri(profile.TrustSchema.GovernanceUri, $"{field}.trustSchema.governanceUri"); - if (profile.TrustSchema.VerificationMethods.Any(string.IsNullOrWhiteSpace)) - throw new TypeFerenceException($"{field}.trustSchema.verificationMethods cannot contain empty values"); - } - foreach (var attestation in profile.Attestations) - { - Required(attestation.Type, $"{field}.attestations.type"); - ValidateUri(attestation.Uri, $"{field}.attestations.uri", allowData: true); - OptionalDigest(attestation.Digest, $"{field}.attestations.digest"); - if (attestation.Size < 0) throw new TypeFerenceException($"{field}.attestations.size cannot be negative"); - } - foreach (var provenance in profile.Provenance) - { - Required(provenance.Relation, $"{field}.provenance.relation"); - Required(provenance.SourceId, $"{field}.provenance.sourceId"); - if (!Uri.TryCreate(provenance.SourceId, UriKind.Absolute, out _)) - throw new TypeFerenceException($"{field}.provenance.sourceId must be an absolute URI"); - OptionalDigest(provenance.SourceDigest, $"{field}.provenance.sourceDigest"); - OptionalUri(provenance.RegistryUri, $"{field}.provenance.registryUri"); - OptionalUri(provenance.StatementUri, $"{field}.provenance.statementUri"); - OptionalUri(provenance.SignatureRef, $"{field}.provenance.signatureRef"); - } - foreach (var key in profile.Metadata.Keys) - if (string.IsNullOrWhiteSpace(key)) throw new TypeFerenceException($"{field}.metadata keys cannot be empty"); - if (profile.SignatureIntent is not null && string.IsNullOrWhiteSpace(profile.SignatureIntent.Algorithm) && string.IsNullOrWhiteSpace(profile.SignatureIntent.KeyRef)) - throw new TypeFerenceException($"{field}.signatureIntent requires algorithm or keyRef"); - OptionalUri(profile.SignatureIntent?.KeyRef, $"{field}.signatureIntent.keyRef"); - } - - private static void ValidateIdentity(string identity, string identityType, string field) - { - // Spec ("Canonical text and ordering"): identities must be ASCII so - // domain alignment does not depend on an implementation's IDN - // handling. Internationalized authorities must be pre-punycoded. - if (identity.Any(c => c > 0x7F)) - throw new TypeFerenceException($"{field} must be ASCII; encode internationalized authorities as punycode"); - ValidateUri(identity, field); - if (identity.StartsWith("did:", StringComparison.Ordinal) && !Did().IsMatch(identity)) - throw new TypeFerenceException($"{field} is not valid DID syntax"); - var expected = identity.StartsWith("did:", StringComparison.Ordinal) ? "did" - : identity.StartsWith("spiffe://", StringComparison.Ordinal) ? "spiffe" - : identity.StartsWith("https://", StringComparison.Ordinal) ? "https" - : identity.StartsWith("dns:", StringComparison.Ordinal) ? "dns" : null; - if (identityType.Length > 0 && KnownIdentityTypes.Contains(identityType) && expected is not null && !string.Equals(identityType, expected, StringComparison.Ordinal)) - throw new TypeFerenceException($"{field} scheme does not match identityType '{identityType}'"); - } - - private static void ValidateUri(string value, string field, bool allowData = false) - { - if (!Uri.TryCreate(value, UriKind.Absolute, out var uri)) throw new TypeFerenceException($"{field} must be an absolute URI"); - if (!allowData && string.Equals(uri.Scheme, "data", StringComparison.OrdinalIgnoreCase)) - throw new TypeFerenceException($"{field} cannot be a data URI"); - if (allowData && uri.Scheme is not ("https" or "data")) - throw new TypeFerenceException($"{field} must use https or data"); - } - - private static void OptionalUri(string? value, string field) - { - if (!string.IsNullOrWhiteSpace(value)) ValidateUri(value, field); - } - - private static void OptionalDigest(string? value, string field) - { - if (value is null) return; - if (!Digest().IsMatch(value)) throw new TypeFerenceException($"{field} must be a lowercase SHA-256, SHA-384, or SHA-512 digest"); - } - - private static void ValidateDetachedJws(string value, string identifier) - { - var segments = value.Split('.'); - if (segments.Length != 3 || segments[0].Length == 0 || segments[1].Length != 0 || segments[2].Length == 0 || - !Base64Url().IsMatch(segments[0]) || !Base64Url().IsMatch(segments[2])) - throw new TypeFerenceException($"Trust signature for {identifier} must be compact detached JWS (protected..signature)"); - } - - private static void Required(string value, string field) - { - if (string.IsNullOrWhiteSpace(value)) throw new TypeFerenceException($"{field} is required"); - } - - private static void EnsureBeneathRoot(string root, string path) - { - if (!path.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) - throw new TypeFerenceException($"Trust configuration must be beneath source root: {path}"); - } - - private static void NormalizeMetadata(TrustConfiguration configuration) - { - if (configuration.Source is not null) configuration.Source.Metadata = CanonicalMetadata(configuration.Source.Metadata); - if (configuration.Bundles is not null) configuration.Bundles.Metadata = CanonicalMetadata(configuration.Bundles.Metadata); - } - - private static object? CanonicalValue(object? value) - { - if (value is null || value is string || value is bool || value is byte || value is sbyte || value is short || value is ushort || - value is int || value is uint || value is long || value is ulong || value is float || value is double || value is decimal) - return value; - if (value is IDictionary dictionary) - { - var result = new SortedDictionary(StringComparer.Ordinal); - foreach (DictionaryEntry entry in dictionary) - { - if (entry.Key is not string key || !MetadataKey().IsMatch(key)) throw new TypeFerenceException("Trust metadata keys must be ASCII identifiers matching [A-Za-z0-9][A-Za-z0-9._-]*"); - result[key] = CanonicalValue(entry.Value); - } - return result; - } - if (value is IEnumerable sequence) return sequence.Cast().Select(CanonicalValue).ToArray(); - return value.ToString(); - } - - [GeneratedRegex("\\{([^{}]+)\\}", RegexOptions.CultureInvariant)] - private static partial Regex Placeholder(); - [GeneratedRegex("^did:[a-z0-9]+:[A-Za-z0-9._:%-]+(?::[A-Za-z0-9._:%-]+)*(?:/[^?#]*)?(?:\\?[^#]*)?(?:#.*)?$", RegexOptions.CultureInvariant)] - private static partial Regex Did(); - [GeneratedRegex("^(?:sha256:[0-9a-f]{64}|sha384:[0-9a-f]{96}|sha512:[0-9a-f]{128})$", RegexOptions.CultureInvariant)] - private static partial Regex Digest(); - [GeneratedRegex("^[A-Za-z0-9_-]+$", RegexOptions.CultureInvariant)] - private static partial Regex Base64Url(); - [GeneratedRegex("^[A-Za-z0-9][A-Za-z0-9._-]*$", RegexOptions.CultureInvariant)] - private static partial Regex MetadataKey(); -} diff --git a/src/TypeFerence.Core/TypeFerence.Core.csproj b/src/TypeFerence.Core/TypeFerence.Core.csproj deleted file mode 100644 index 37c03cd..0000000 --- a/src/TypeFerence.Core/TypeFerence.Core.csproj +++ /dev/null @@ -1,4 +0,0 @@ - - TypeFerence type system and deterministic compiler. - - diff --git a/src/TypeFerence.Core/TypeResolver.cs b/src/TypeFerence.Core/TypeResolver.cs deleted file mode 100644 index 404a080..0000000 --- a/src/TypeFerence.Core/TypeResolver.cs +++ /dev/null @@ -1,254 +0,0 @@ -using System.Text.Json; - -namespace TypeFerence.Core; - -public sealed class TypeResolver(IReadOnlyDictionary resources) -{ - private readonly Dictionary _componentCache = new(StringComparer.Ordinal); - private readonly Dictionary _interfaceCache = new(StringComparer.Ordinal); - private readonly Dictionary> _slotDepths = new(StringComparer.Ordinal); - private readonly Dictionary> _skillDepths = new(StringComparer.Ordinal); - - public IReadOnlyList ResolveAll() - { - foreach (var item in resources.Values.Where(x => x.Kind == "skill").OrderBy(x => x.Id, StringComparer.Ordinal)) - ValidateSkillImplementation(item); - foreach (var item in resources.Values.Where(x => x.Kind == "interface").OrderBy(x => x.Id, StringComparer.Ordinal)) - ResolveInterface(item.Id, new HashSet(StringComparer.Ordinal)); - foreach (var item in resources.Values.Where(x => x.Kind == "profile").OrderBy(x => x.Id, StringComparer.Ordinal)) - ResolveComponent(item.Id, new HashSet(StringComparer.Ordinal), requireAgent: false); - return resources.Values - .Where(x => x.Kind == "agent") - .OrderBy(x => x.Id, StringComparer.Ordinal) - .Select(x => Resolve(x.Id)) - .ToArray(); - } - - public ResolvedAgent Resolve(string id) => ResolveComponent(id, new HashSet(StringComparer.Ordinal), requireAgent: true); - - private ResolvedAgent ResolveComponent(string id, HashSet visiting, bool requireAgent) - { - if (requireAgent) Require(id, "agent"); - if (_componentCache.TryGetValue(id, out var cached)) return cached; - var current = RequireEmbeddable(id); - if (!visiting.Add(id)) throw new TypeFerenceException($"Embedding cycle detected at {id}"); - if (current.Embeds.Count != current.Embeds.Distinct(StringComparer.Ordinal).Count()) - throw new TypeFerenceException($"{id}: a {current.Kind} cannot embed the same resource more than once"); - - try - { - var embedded = current.Embeds.Select(x => - { - var embeddedResource = RequireEmbeddable(x); - if (current.Kind == "profile" && embeddedResource.Kind != "profile") - throw new TypeFerenceException($"{id}: profiles can only embed profiles"); - return ResolveComponent(x, visiting, requireAgent: false); - }).ToArray(); - var (slots, slotDepths) = MergeSlots(id, current, embedded); - var norms = Distinct(embedded.SelectMany(x => x.WorkingNorms).Concat(current.WorkingNorms)); - var contexts = Distinct(embedded.SelectMany(x => x.ContextFiles).Concat(current.ContextFiles).Select(NormalizePath)); - var (skills, skillDepths) = MergeSkills(id, current, embedded, contexts); - var satisfies = resources.Values - .Where(x => x.Kind == "interface") - .OrderBy(x => x.Id, StringComparer.Ordinal) - .Where(x => Satisfies(ResolveInterface(x.Id, new HashSet(StringComparer.Ordinal)), slots, skills)) - .Select(x => x.Id) - .ToArray(); - - var provenance = embedded.SelectMany(x => x.Provenance.Where(IsPromotedProvenance)).ToList(); - provenance.AddRange(current.Embeds.Select(x => new ProvenanceEntry($"embeds.{x}", id))); - if (!string.IsNullOrWhiteSpace(current.DisplayName)) provenance.Add(new("displayName", id)); - if (!string.IsNullOrWhiteSpace(current.Description)) provenance.Add(new("description", id)); - provenance.AddRange(current.Slots.Keys.Select(x => new ProvenanceEntry($"slots.{x}", id))); - provenance.AddRange(current.WorkingNorms.Select(_ => new ProvenanceEntry("workingNorms", id))); - provenance.AddRange(current.ContextFiles.Select(_ => new ProvenanceEntry("contextFiles", id))); - provenance.AddRange(satisfies.Select(x => new ProvenanceEntry($"satisfies.{x}", id))); - - var resolved = new ResolvedAgent - { - Id = id, - DisplayName = string.IsNullOrWhiteSpace(current.DisplayName) ? id : current.DisplayName, - Description = current.Description, - Emit = current.Emit, - Embeds = current.Embeds.ToArray(), - Satisfies = satisfies, - Slots = slots, - WorkingNorms = norms, - ContextFiles = contexts, - Skills = skills.Values.OrderBy(x => x.CapabilityId, StringComparer.Ordinal).Select(x => x.WithDispatch(id)).ToArray(), - Provenance = provenance - }; - _slotDepths[id] = slotDepths; - _skillDepths[id] = skillDepths; - return _componentCache[id] = resolved; - } - finally - { - visiting.Remove(id); - } - } - - private (SortedDictionary Values, IReadOnlyDictionary Depths) MergeSlots( - string id, - ResourceDocument current, - ResolvedAgent[] embedded) - { - var result = new SortedDictionary(StringComparer.Ordinal); - var depths = new Dictionary(StringComparer.Ordinal); - foreach (var group in embedded.SelectMany(x => x.Slots.Select(pair => - (Agent: x.Id, pair.Key, pair.Value, Depth: _slotDepths[x.Id][pair.Key] + 1))) - .GroupBy(x => x.Key, StringComparer.Ordinal)) - { - var depth = group.Min(x => x.Depth); - var nearest = group.Where(x => x.Depth == depth).ToArray(); - if (nearest.Length > 1 && !current.Slots.ContainsKey(group.Key)) - throw new TypeFerenceException($"{id}: embedded slot '{group.Key}' is ambiguous between {string.Join(", ", nearest.Select(x => x.Agent))}; declare it on {id} to resolve the conflict"); - result[group.Key] = nearest[0].Value; - depths[group.Key] = depth; - } - foreach (var pair in current.Slots) - { - result[pair.Key] = NormalizePath(pair.Value); - depths[pair.Key] = 0; - } - return (result, depths); - } - - private (Dictionary Values, IReadOnlyDictionary Depths) MergeSkills( - string id, - ResourceDocument current, - ResolvedAgent[] embedded, - IReadOnlyList contexts) - { - var result = new Dictionary(StringComparer.Ordinal); - var depths = new Dictionary(StringComparer.Ordinal); - var localCapabilities = current.Skills.Select(x => ResolveCapabilityId(x, id)).ToHashSet(StringComparer.Ordinal); - if (localCapabilities.Count != current.Skills.Count) - throw new TypeFerenceException($"{id}: a capability cannot be bound more than once"); - foreach (var group in embedded.SelectMany(x => x.Skills.Select(skill => - (Agent: x.Id, Skill: skill, Depth: _skillDepths[x.Id][skill.CapabilityId] + 1))) - .GroupBy(x => x.Skill.CapabilityId, StringComparer.Ordinal)) - { - var depth = group.Min(x => x.Depth); - var nearest = group.Where(x => x.Depth == depth).ToArray(); - if (nearest.Length > 1 && !localCapabilities.Contains(group.Key)) - throw new TypeFerenceException($"{id}: embedded capability '{group.Key}' is ambiguous between {string.Join(", ", nearest.Select(x => x.Agent))}; bind the capability on {id} to resolve the conflict"); - result[group.Key] = nearest[0].Skill; - depths[group.Key] = depth; - } - - foreach (var binding in current.Skills) - { - var implementation = Require(binding.Ref, "skill"); - var capabilityId = ResolveCapabilityId(binding, id); - var capability = Require(capabilityId, "capability"); - EnsureImplementsCapability(capability, implementation, id); - if (result.TryGetValue(capabilityId, out var promoted)) EnsureSameCapability(promoted, capability, id); - result[capabilityId] = new ResolvedSkill - { - CapabilityId = capabilityId, - ImplementationId = implementation.Id, - Description = implementation.Description, - Instructions = implementation.Instructions, - InputSchema = CanonicalJson(implementation.InputSchema), - OutputSchema = CanonicalJson(implementation.OutputSchema), - ContextFiles = Distinct(contexts.Concat(implementation.ContextFiles.Select(NormalizePath))), - Provenance = [new("skill.capability", capabilityId), new("skill.implementation", implementation.Id)] - }; - depths[capabilityId] = 0; - } - return (result, depths); - } - - private string ResolveCapabilityId(SkillBinding binding, string agent) - { - var implementation = Require(binding.Ref, "skill"); - if (string.IsNullOrWhiteSpace(implementation.Binds)) - throw new TypeFerenceException($"{agent}: skill {implementation.Id} does not bind a capability"); - if (binding.Capability is not null && binding.Capability != implementation.Binds) - throw new TypeFerenceException($"{agent}: binding declares capability {binding.Capability}, but skill {implementation.Id} binds {implementation.Binds}"); - return binding.Capability ?? implementation.Binds; - } - - private void ValidateSkillImplementation(ResourceDocument implementation) - { - if (string.IsNullOrWhiteSpace(implementation.Binds)) - throw new TypeFerenceException($"Skill {implementation.Id} does not bind a capability"); - var capability = Require(implementation.Binds, "capability"); - EnsureImplementsCapability(capability, implementation, implementation.Id); - } - - private InterfaceContract ResolveInterface(string id, HashSet visiting) - { - if (_interfaceCache.TryGetValue(id, out var cached)) return cached; - var current = Require(id, "interface"); - if (!visiting.Add(id)) throw new TypeFerenceException($"Interface embedding cycle detected at {id}"); - try - { - var embedded = current.Embeds.Select(x => ResolveInterface(x, visiting)).ToArray(); - foreach (var capability in current.RequiresCapabilities) Require(capability, "capability"); - return _interfaceCache[id] = new( - Distinct(embedded.SelectMany(x => x.Slots).Concat(current.RequiresSlots)), - Distinct(embedded.SelectMany(x => x.Skills).Concat(current.RequiresCapabilities))); - } - finally - { - visiting.Remove(id); - } - } - - private static bool Satisfies(InterfaceContract contract, IReadOnlyDictionary slots, IReadOnlyDictionary skills) => - contract.Slots.All(slots.ContainsKey) && contract.Skills.All(skills.ContainsKey); - - private ResourceDocument Require(string id, string kind) - { - if (!resources.TryGetValue(id, out var resource) || resource.Kind != kind) throw new TypeFerenceException($"Missing {kind}: {id}"); - return resource; - } - - private ResourceDocument RequireEmbeddable(string id) - { - if (!resources.TryGetValue(id, out var resource) || resource.Kind is not ("agent" or "profile")) - throw new TypeFerenceException($"Missing embeddable resource: {id}"); - return resource; - } - - private static void EnsureSameCapability(ResolvedSkill promoted, ResourceDocument capability, string agent) - { - if (promoted.InputSchema != CanonicalJson(capability.InputSchema) || promoted.OutputSchema != CanonicalJson(capability.OutputSchema)) - throw new TypeFerenceException($"{agent}: promoted implementation {promoted.ImplementationId} changes the public contract of {capability.Id}"); - } - - private static void EnsureImplementsCapability(ResourceDocument capability, ResourceDocument implementation, string agent) - { - if (implementation.Binds != capability.Id) - throw new TypeFerenceException($"{agent}: implementation {implementation.Id} binds {implementation.Binds}, not capability {capability.Id}"); - if (CanonicalJson(capability.InputSchema) != CanonicalJson(implementation.InputSchema) || - CanonicalJson(capability.OutputSchema) != CanonicalJson(implementation.OutputSchema)) - throw new TypeFerenceException($"{agent}: implementation {implementation.Id} changes the public contract of {capability.Id}"); - } - - private static string NormalizePath(string value) => value.Replace('\\', '/').TrimStart('/'); - private static IReadOnlyList Distinct(IEnumerable values) => values.Distinct(StringComparer.Ordinal).ToArray(); - private static string CanonicalJson(string json) => JsonSerializer.Serialize(JsonDocument.Parse(json).RootElement); - private static bool IsPromotedProvenance(ProvenanceEntry entry) => - entry.Field is not ("displayName" or "description") && - !entry.Field.StartsWith("satisfies.", StringComparison.Ordinal); - private sealed record InterfaceContract(IReadOnlyList Slots, IReadOnlyList Skills); -} - -internal static class ResolvedSkillExtensions -{ - internal static ResolvedSkill WithDispatch(this ResolvedSkill skill, string agentId) => new() - { - DispatchName = $"{agentId.Split('/').Last().Split('@')[0]}.{skill.CapabilityId.Split('/').Last().Split('@')[0]}", - CapabilityId = skill.CapabilityId, - ImplementationId = skill.ImplementationId, - Description = skill.Description, - Instructions = skill.Instructions, - InputSchema = skill.InputSchema, - OutputSchema = skill.OutputSchema, - ContextFiles = skill.ContextFiles, - Provenance = skill.Provenance - }; -} diff --git a/tests/TypeFerence.Tests/CanonicalizationTests.cs b/tests/TypeFerence.Tests/CanonicalizationTests.cs deleted file mode 100644 index dc11775..0000000 --- a/tests/TypeFerence.Tests/CanonicalizationTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -using TypeFerence.Core; -using Xunit; - -namespace TypeFerence.Tests; - -/// -/// Targeted coverage for the ADR-0004 canonicalization rulings as implemented -/// in the reference implementation: canonical string ordering, the -/// separator-independent digest, and the ASCII key-space validations. -/// -public sealed class CanonicalizationTests -{ - [Fact] - public void CanonicalOrder_SortsByCodePoint_NotUtf16CodeUnits() - { - // U+FFFD sorts below U+10000 in code point order, but ABOVE it in - // UTF-16 code-unit order (surrogates start at 0xD800 < 0xFFFD). - var supplementary = char.ConvertFromUtf32(0x10000); - Assert.True(CanonicalOrder.Instance.Compare("�", supplementary) < 0); - Assert.True(StringComparer.Ordinal.Compare("�", supplementary) > 0); - - // ASCII behavior matches ordinal exactly. - Assert.True(CanonicalOrder.Instance.Compare("a", "b") < 0); - Assert.True(CanonicalOrder.Instance.Compare("notes.md", "notes/x") < 0); - Assert.True(CanonicalOrder.Instance.Compare("notes/x", "notes0.md") < 0); - Assert.Equal(0, CanonicalOrder.Instance.Compare("same", "same")); - } - - [Fact] - public void HashDirectory_OrdersByRelativeForwardSlashPath() - { - // A tree whose names straddle the '/'..'\\' ASCII range hashed - // per-file in the spec-defined order. If the implementation sorted - // platform paths (backslashes on Windows), "notesA.md" would sort - // before "notes/deep.md" here and the digest would differ per OS. - using var temp = new TempDirectory(); - Directory.CreateDirectory(Path.Combine(temp.Path, "notes")); - File.WriteAllText(Path.Combine(temp.Path, "notes", "deep.md"), "deep"); - File.WriteAllText(Path.Combine(temp.Path, "notesA.md"), "A"); - File.WriteAllText(Path.Combine(temp.Path, "notes.md"), "dot"); - - var expectedPayload = "notes.md\0dot\0notes/deep.md\0deep\0notesA.md\0A\0"; - var expected = Convert.ToHexString( - System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(expectedPayload))) - .ToLowerInvariant(); - Assert.Equal(expected, TypeFerenceCompiler.HashDirectory(temp.Path)); - } - - [Fact] - public void NonAsciiSlotNames_AreRejected() - { - using var temp = new TempDirectory(); - File.WriteAllText(Path.Combine(temp.Path, "policy.md"), "p"); - File.WriteAllText(Path.Combine(temp.Path, "agent.yaml"), - """ - schemaVersion: 3 - kind: agent - id: t/agent@1.0.0 - slots: - règlePolitique: policy.md - """); - var ex = Assert.Throws(() => new ResourceLoader().Load(temp.Path)); - Assert.Contains("ASCII identifier", ex.Message); - } - - [Fact] - public void NonAsciiRequiredSlotNames_AreRejected() - { - using var temp = new TempDirectory(); - File.WriteAllText(Path.Combine(temp.Path, "interface.yaml"), - """ - schemaVersion: 3 - kind: interface - id: t/interfaces/i@1.0.0 - requiresSlots: - - política - """); - var ex = Assert.Throws(() => new ResourceLoader().Load(temp.Path)); - Assert.Contains("ASCII identifier", ex.Message); - } - - [Fact] - public void UppercaseYamlExtension_IsNotLoaded() - { - using var temp = new TempDirectory(); - File.WriteAllText(Path.Combine(temp.Path, "agent.yaml"), - """ - schemaVersion: 3 - kind: agent - id: t/agent@1.0.0 - """); - // On a case-insensitive file system, EnumerateFiles("*.yaml") would - // match this without the explicit ordinal extension pin. - File.WriteAllText(Path.Combine(temp.Path, "SHOUTING.YAML"), "not a resource"); - var resources = new ResourceLoader().Load(temp.Path); - Assert.Single(resources); - } - - private sealed class TempDirectory : IDisposable - { - public string Path { get; } = Directory.CreateTempSubdirectory("typeference-test-").FullName; - public void Dispose() => Directory.Delete(Path, true); - } -} diff --git a/tests/TypeFerence.Tests/CompilerTests.cs b/tests/TypeFerence.Tests/CompilerTests.cs deleted file mode 100644 index 12f753f..0000000 --- a/tests/TypeFerence.Tests/CompilerTests.cs +++ /dev/null @@ -1,780 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Diagnostics; -using ModelContextProtocol.Client; -using ModelContextProtocol.Protocol; -using TypeFerence.Core; -using Xunit; - -namespace TypeFerence.Tests; - -public sealed class CompilerTests -{ - private static string Root => FindRoot(); - private static string Example => Path.Combine(Root, "examples", "helio"); - - [Fact] - public void Profiles_AreReusableAndNotEmittedAsAgents() - { - var agents = new TypeFerenceCompiler().Validate(Example); - Assert.Equal(2, agents.Count); - Assert.DoesNotContain(agents, x => x.Id == "helio/profiles/enterprise-defaults@1.0.0"); - Assert.All(agents, x => Assert.True(x.Emit)); - Assert.All(agents, x => Assert.Contains("helio/profiles/", x.Embeds.Single())); - } - - [Fact] - public void ConcreteAgents_PromoteEmbeddedBehavior() - { - var agents = new TypeFerenceCompiler().Validate(Example).Where(x => x.Emit); - Assert.All(agents, x => Assert.Contains("organization", x.Slots.Keys)); - } - - [Fact] - public void Override_PreservesCapabilityAndUsesDerivedDispatch() - { - var agent = new TypeFerenceCompiler().Validate(Example).Single(x => x.Id == "helio/payments-repo-agent@1.0.0"); - var skill = Assert.Single(agent.Skills); - Assert.Equal("helio/capabilities/repository-status@1.0.0", skill.CapabilityId); - Assert.Equal("helio/skills/payments-repository-status@1.0.0", skill.ImplementationId); - Assert.Equal("payments-repo-agent.repository-status", skill.DispatchName); - } - - [Fact] - public void InvocationPackage_ContainsResolvedProvenance() - { - var agent = new TypeFerenceCompiler().Validate(Example).Single(x => x.Id == "helio/payments-repo-agent@1.0.0"); - using var args = JsonDocument.Parse("{\"focus\":\"release\"}"); - var package = TypeFerenceCompiler.Invoke(agent, agent.Skills.Single(), args.RootElement); - Assert.Equal("payments-repo-agent.repository-status", package.DispatchName); - Assert.Contains(package.ContextReferences, x => x.EndsWith("payments-service.md", StringComparison.Ordinal)); - Assert.Contains(package.Provenance, x => x.Field == "skill.implementation"); - } - - [Fact] - public void Compilation_IsDeterministicAndProducesNativeShapes() - { - using var one = new TempDirectory(); - using var two = new TempDirectory(); - var compiler = new TypeFerenceCompiler(); - compiler.Build(Example, one.Path, Enum.GetValues(), new ArdPublicationOptions("helio.example")); - compiler.Build(Example, two.Path, Enum.GetValues(), new ArdPublicationOptions("helio.example")); - Assert.Equal("a22f5410ba5f8e172c8d25dd2ff3efb867b212753723fd2e3b1faca21d2b3963", TypeFerenceCompiler.HashDirectory(one.Path)); - Assert.Equal(TypeFerenceCompiler.HashDirectory(one.Path), TypeFerenceCompiler.HashDirectory(two.Path)); - Assert.False(DiffResult.Compare(one.Path, two.Path).Different); - Assert.True(File.Exists(Path.Combine(one.Path, "codex", "executive-assistant", ".agents", "skills", "prepare-brief", "SKILL.md"))); - Assert.True(File.Exists(Path.Combine(one.Path, "codex", "executive-assistant", ".typeference", "bundle.json"))); - Assert.True(File.Exists(Path.Combine(one.Path, "copilot", "executive-assistant", ".github", "agents", "executive-assistant.agent.md"))); - Assert.True(File.Exists(Path.Combine(one.Path, "copilot", "executive-assistant", ".typeference", "bundle.json"))); - Assert.True(File.Exists(Path.Combine(one.Path, "cursor", "executive-assistant", ".cursor", "rules", "executive-assistant.mdc"))); - Assert.True(File.Exists(Path.Combine(one.Path, "cursor", "executive-assistant", ".typeference", "bundle.json"))); - var catalogPath = Path.Combine(one.Path, "ard", "ai-catalog.json"); - Assert.True(File.Exists(catalogPath)); - using var catalog = JsonDocument.Parse(File.ReadAllText(catalogPath)); - Assert.Equal("1.0", catalog.RootElement.GetProperty("specVersion").GetString()); - var entries = catalog.RootElement.GetProperty("entries"); - Assert.Equal(9, entries.GetArrayLength()); - Assert.Single(entries.EnumerateArray(), entry => - entry.GetProperty("type").GetString() == "application/vnd.typeference.source-package+json"); - var targetEntries = entries.EnumerateArray().Where(entry => - entry.GetProperty("type").GetString() == "application/vnd.typeference.target-bundle+json").ToArray(); - Assert.Equal(8, targetEntries.Length); - Assert.All(targetEntries, entry => - { - Assert.True(entry.TryGetProperty("data", out var data)); - Assert.Contains(data.GetProperty("target").GetString(), new[] { "neutral", "codex", "copilot", "cursor" }); - Assert.Equal("derivedFrom", entry.GetProperty("trustManifest").GetProperty("provenance")[0].GetProperty("relation").GetString()); - }); - File.AppendAllText(Path.Combine(two.Path, "neutral", "executive-assistant", "AGENTS.md"), "changed"); - Assert.True(DiffResult.Compare(one.Path, two.Path).Different); - } - - [Fact] - public void TrustConfiguration_EnrichesSourceAndBundlesDeterministically() - { - using var source = new TempDirectory(); - using var one = new TempDirectory(); - using var two = new TempDirectory(); - CopyDirectory(Example, source.Path); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), TrustConfiguration()); - - var compiler = new TypeFerenceCompiler(); - compiler.Build(source.Path, one.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example")); - compiler.Build(source.Path, two.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example")); - Assert.Equal(TypeFerenceCompiler.HashDirectory(one.Path), TypeFerenceCompiler.HashDirectory(two.Path)); - Assert.False(DiffResult.Compare(one.Path, two.Path).Different); - - using var catalog = JsonDocument.Parse(File.ReadAllText(Path.Combine(one.Path, "ard", "ai-catalog.json"))); - var entries = catalog.RootElement.GetProperty("entries").EnumerateArray().ToArray(); - var sourceEntry = Assert.Single(entries, x => x.GetProperty("type").GetString() == "application/vnd.typeference.source-package+json"); - var sourceTrust = sourceEntry.GetProperty("trustManifest"); - Assert.Equal("did:web:helio.example:typeference:source:helio", sourceTrust.GetProperty("identity").GetString()); - Assert.Equal("https://slsa.dev/provenance/v1", sourceTrust.GetProperty("attestations")[0].GetProperty("type").GetString()); - Assert.Equal("external", sourceTrust.GetProperty("metadata") - .GetProperty("com.github.buchk.typeference.signatureIntent").GetProperty("status").GetString()); - - var bundles = entries.Where(x => x.GetProperty("type").GetString() == "application/vnd.typeference.target-bundle+json").ToArray(); - Assert.Equal(2, bundles.Length); - Assert.All(bundles, entry => - { - var trust = entry.GetProperty("trustManifest"); - Assert.StartsWith("spiffe://helio.example/typeference/neutral/", trust.GetProperty("identity").GetString(), StringComparison.Ordinal); - Assert.Equal("spiffe", trust.GetProperty("identityType").GetString()); - Assert.Equal("urn:trust:helio-agent-governance-v1", trust.GetProperty("trustSchema").GetProperty("identifier").GetString()); - Assert.Equal("derivedFrom", trust.GetProperty("provenance")[0].GetProperty("relation").GetString()); - Assert.Equal("publishedFrom", trust.GetProperty("provenance")[1].GetProperty("relation").GetString()); - var metadata = trust.GetProperty("metadata"); - Assert.Equal("typeference-directory-v1", metadata.GetProperty("com.github.buchk.typeference.artifactDigest").GetProperty("scheme").GetString()); - Assert.Equal("sha256:" + new string('c', 64), metadata.GetProperty("com.helio.verification").GetProperty("policyDigest").GetString()); - }); - } - - [Fact] - public void ExternalDetachedJwsSignatures_AreImportedWithoutKeys() - { - using var source = new TempDirectory(); - using var output = new TempDirectory(); - using var unsignedOutput = new TempDirectory(); - using var signatureDirectory = new TempDirectory(); - CopyDirectory(Example, source.Path); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), TrustConfiguration()); - new TypeFerenceCompiler().Build( - source.Path, unsignedOutput.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example")); - var signaturesPath = Path.Combine(signatureDirectory.Path, "signatures.json"); - File.WriteAllText(signaturesPath, """ -{ - "urn:air:helio.example:typeference:neutral:executive-assistant": "eyJhbGciOiJFUzI1NiJ9..c2ln", - "urn:air:helio.example:typeference:neutral:payments-repo-agent": "eyJhbGciOiJFUzI1NiJ9..c2ln" -} -"""); - - new TypeFerenceCompiler().Build( - source.Path, - output.Path, - [CompilationTarget.Neutral], - new ArdPublicationOptions("helio.example", TrustSignaturesPath: signaturesPath)); - using var catalog = JsonDocument.Parse(File.ReadAllText(Path.Combine(output.Path, "ard", "ai-catalog.json"))); - var unsignedCatalog = JsonNode.Parse(File.ReadAllText(Path.Combine(unsignedOutput.Path, "ard", "ai-catalog.json")))!; - var unsignedManifests = unsignedCatalog["entries"]!.AsArray() - .Where(x => x!["type"]!.GetValue() == "application/vnd.typeference.target-bundle+json") - .ToDictionary(x => x!["identifier"]!.GetValue(), x => x!["trustManifest"]!.DeepClone(), StringComparer.Ordinal); - var bundles = catalog.RootElement.GetProperty("entries").EnumerateArray() - .Where(x => x.GetProperty("type").GetString() == "application/vnd.typeference.target-bundle+json"); - Assert.All(bundles, entry => - { - var trust = entry.GetProperty("trustManifest"); - Assert.Equal("eyJhbGciOiJFUzI1NiJ9..c2ln", trust.GetProperty("signature").GetString()); - Assert.Equal("external", trust.GetProperty("metadata") - .GetProperty("com.github.buchk.typeference.signatureIntent").GetProperty("status").GetString()); - var signedPayload = JsonNode.Parse(trust.GetRawText())!.AsObject(); - signedPayload.Remove("signature"); - Assert.True(JsonNode.DeepEquals(unsignedManifests[entry.GetProperty("identifier").GetString()!], signedPayload)); - }); - } - - [Fact] - public void TrustSignaturesInsideSource_AreRejectedToPreventCycles() - { - using var source = new TempDirectory(); - using var output = new TempDirectory(); - CopyDirectory(Example, source.Path); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), TrustConfiguration()); - var signatures = Path.Combine(source.Path, "signatures.json"); - File.WriteAllText(signatures, "{}"); - var exception = Assert.Throws(() => new TypeFerenceCompiler().Build( - source.Path, output.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example", TrustSignaturesPath: signatures))); - Assert.Contains("digest/signature cycle", exception.Message, StringComparison.OrdinalIgnoreCase); - } - - [Theory] - [InlineData("sha256:abc")] - [InlineData("SHA256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")] - public void TrustConfiguration_RejectsMalformedDigests(string digest) - { - using var source = new TempDirectory(); - CopyDirectory(Example, source.Path); - var valid = "sha256:" + new string('a', 64); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), TrustConfiguration().Replace(valid, digest, StringComparison.Ordinal)); - var exception = Assert.Throws(() => new TypeFerenceCompiler().Validate(source.Path)); - Assert.Contains("lowercase SHA-256", exception.Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void TrustSignatureMap_RejectsUnknownEntries() - { - using var source = new TempDirectory(); - using var output = new TempDirectory(); - using var signatureDirectory = new TempDirectory(); - CopyDirectory(Example, source.Path); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), TrustConfiguration()); - var signatures = Path.Combine(signatureDirectory.Path, "signatures.json"); - File.WriteAllText(signatures, "{\"urn:air:helio.example:typeference:neutral:unknown\":\"e30..c2ln\"}"); - var exception = Assert.Throws(() => new TypeFerenceCompiler().Build( - source.Path, output.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example", TrustSignaturesPath: signatures))); - Assert.Contains("does not match", exception.Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void RequiredTrustSignatures_MustCoverEveryConfiguredEntry() - { - using var source = new TempDirectory(); - using var output = new TempDirectory(); - CopyDirectory(Example, source.Path); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), TrustConfiguration(bundleSignatureRequired: true)); - var exception = Assert.Throws(() => new TypeFerenceCompiler().Build( - source.Path, output.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example"))); - Assert.Contains("signature is required", exception.Message, StringComparison.OrdinalIgnoreCase); - - new TypeFerenceCompiler().Build( - source.Path, - output.Path, - [CompilationTarget.Neutral], - new ArdPublicationOptions("helio.example", AllowUnsignedTrust: true)); - using var catalog = JsonDocument.Parse(File.ReadAllText(Path.Combine(output.Path, "ard", "ai-catalog.json"))); - var bundles = catalog.RootElement.GetProperty("entries").EnumerateArray() - .Where(x => x.GetProperty("type").GetString() == "application/vnd.typeference.target-bundle+json"); - Assert.All(bundles, x => Assert.False(x.GetProperty("trustManifest").TryGetProperty("signature", out _))); - } - - [Theory] - [InlineData("spiffe://other.example/typeference/{target}/{agent}", "does not align")] - [InlineData("spiffe://helio.example/typeference/{agent}", "must contain {agent} and {target}")] - public void InvalidTrustIdentities_AreRejected(string identityTemplate, string expected) - { - using var source = new TempDirectory(); - using var output = new TempDirectory(); - CopyDirectory(Example, source.Path); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), TrustConfiguration(identityTemplate: identityTemplate)); - var exception = Assert.Throws(() => new TypeFerenceCompiler().Build( - source.Path, output.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example"))); - Assert.Contains(expected, exception.Message, StringComparison.OrdinalIgnoreCase); - } - - [Theory] - [InlineData("dns:other.example", "dns", "does not align")] - [InlineData("did:key:z6MkhExample", "did", "does not expose")] - public void TrustIdentity_MustExposeThePublisherDomain(string identity, string identityType, string expected) - { - using var source = new TempDirectory(); - using var output = new TempDirectory(); - CopyDirectory(Example, source.Path); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), SourceTrustConfiguration(identity, identityType)); - var exception = Assert.Throws(() => new TypeFerenceCompiler().Build( - source.Path, output.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example"))); - Assert.Contains(expected, exception.Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void CustomIdentityTypeHints_RemainExtensible() - { - using var source = new TempDirectory(); - using var output = new TempDirectory(); - CopyDirectory(Example, source.Path); - var configuration = TrustConfiguration().Replace("identityType: spiffe", "identityType: helio-workload-v1", StringComparison.Ordinal); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), configuration); - new TypeFerenceCompiler().Build( - source.Path, output.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example")); - using var catalog = JsonDocument.Parse(File.ReadAllText(Path.Combine(output.Path, "ard", "ai-catalog.json"))); - var bundle = catalog.RootElement.GetProperty("entries").EnumerateArray() - .First(x => x.GetProperty("type").GetString() == "application/vnd.typeference.target-bundle+json"); - Assert.Equal("helio-workload-v1", bundle.GetProperty("trustManifest").GetProperty("identityType").GetString()); - } - - [Fact] - public void DnsIdentity_CanBindToPublisherDomain() - { - using var source = new TempDirectory(); - using var output = new TempDirectory(); - CopyDirectory(Example, source.Path); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), SourceTrustConfiguration("dns:helio.example", "dns")); - new TypeFerenceCompiler().Build( - source.Path, output.Path, [CompilationTarget.Neutral], new ArdPublicationOptions("helio.example")); - using var catalog = JsonDocument.Parse(File.ReadAllText(Path.Combine(output.Path, "ard", "ai-catalog.json"))); - Assert.Equal("dns:helio.example", catalog.RootElement.GetProperty("entries")[0] - .GetProperty("trustManifest").GetProperty("identity").GetString()); - } - - [Fact] - public async Task CliBuild_AcceptsExplicitTrustConfiguration() - { - using var source = new TempDirectory(); - using var output = new TempDirectory(); - CopyDirectory(Example, source.Path); - var trustPath = Path.Combine(source.Path, "enterprise.trust.yaml"); - File.WriteAllText(trustPath, TrustConfiguration()); - File.WriteAllText(Path.Combine(source.Path, "typeference.trust.yaml"), SourceTrustConfiguration("did:web:other.example:unused", "did")); - Assert.Equal(0, await RunCli("validate", source.Path, "--trust-config", trustPath)); - Assert.Equal(0, await RunCli("build", source.Path, "--target", "neutral", "--out", output.Path, - "--emit-ard", "--publisher-domain", "helio.example", "--trust-config", trustPath)); - using var catalog = JsonDocument.Parse(File.ReadAllText(Path.Combine(output.Path, "ard", "ai-catalog.json"))); - Assert.All(catalog.RootElement.GetProperty("entries").EnumerateArray(), x => Assert.True(x.TryGetProperty("trustManifest", out _))); - } - - [Theory] - [InlineData("--trust-config")] - [InlineData("--trust-signatures")] - public async Task CliTrustOptions_RequireValues(string option) - { - using var output = new TempDirectory(); - Assert.Equal(2, await RunCli("build", Example, "--target", "neutral", "--out", output.Path, - "--emit-ard", "--publisher-domain", "helio.example", option)); - } - - [Fact] - public void EmbeddingCycles_AreRejected() - { - var resources = new Dictionary - { - ["test/a@1.0.0"] = new() { SchemaVersion = 3, Kind = "agent", Id = "test/a@1.0.0", Embeds = ["test/b@1.0.0"] }, - ["test/b@1.0.0"] = new() { SchemaVersion = 3, Kind = "agent", Id = "test/b@1.0.0", Embeds = ["test/a@1.0.0"] } - }; - Assert.Contains("cycle", Assert.Throws(() => new TypeResolver(resources).ResolveAll()).Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void MultipleEmbedding_RequiresExplicitConflictResolution() - { - var resources = new Dictionary - { - ["test/left@1.0.0"] = new() { SchemaVersion = 3, Kind = "agent", Id = "test/left@1.0.0", Slots = { ["owner"] = "left.md" } }, - ["test/right@1.0.0"] = new() { SchemaVersion = 3, Kind = "agent", Id = "test/right@1.0.0", Slots = { ["owner"] = "right.md" } }, - ["test/ambiguous@1.0.0"] = new() - { - SchemaVersion = 3, - Kind = "agent", - Id = "test/ambiguous@1.0.0", - Embeds = ["test/left@1.0.0", "test/right@1.0.0"] - } - }; - Assert.Contains("ambiguous", Assert.Throws(() => new TypeResolver(resources).Resolve("test/ambiguous@1.0.0")).Message, StringComparison.OrdinalIgnoreCase); - - resources["test/ambiguous@1.0.0"].Slots["owner"] = "outer.md"; - Assert.Equal("outer.md", new TypeResolver(resources).Resolve("test/ambiguous@1.0.0").Slots["owner"]); - } - - [Fact] - public void MultipleEmbedding_PrefersTheShallowestPromotedMember() - { - var capability = new ResourceDocument - { - SchemaVersion = 3, - Kind = "capability", - Id = "test/capabilities/status@1.0.0" - }; - var baseSkill = new ResourceDocument - { - SchemaVersion = 3, - Kind = "skill", - Id = "test/skills/status@1.0.0", - Binds = capability.Id - }; - var specializedSkill = new ResourceDocument - { - SchemaVersion = 3, - Kind = "skill", - Id = "test/skills/special-status@1.0.0", - Binds = capability.Id - }; - var resources = new Dictionary - { - [capability.Id] = capability, - [baseSkill.Id] = baseSkill, - [specializedSkill.Id] = specializedSkill, - ["test/deep@1.0.0"] = new() - { - SchemaVersion = 3, - Kind = "agent", - Id = "test/deep@1.0.0", - Slots = { ["owner"] = "deep.md" }, - Skills = [new SkillBinding { Ref = baseSkill.Id }] - }, - ["test/middle@1.0.0"] = new() - { - SchemaVersion = 3, - Kind = "agent", - Id = "test/middle@1.0.0", - Embeds = ["test/deep@1.0.0"] - }, - ["test/shallow@1.0.0"] = new() - { - SchemaVersion = 3, - Kind = "agent", - Id = "test/shallow@1.0.0", - Slots = { ["owner"] = "shallow.md" }, - Skills = [new SkillBinding { Ref = specializedSkill.Id, Capability = capability.Id }] - }, - ["test/outer@1.0.0"] = new() - { - SchemaVersion = 3, - Kind = "agent", - Id = "test/outer@1.0.0", - Embeds = ["test/middle@1.0.0", "test/shallow@1.0.0"] - } - }; - - var resolved = new TypeResolver(resources).Resolve("test/outer@1.0.0"); - Assert.Equal("shallow.md", resolved.Slots["owner"]); - Assert.Equal(specializedSkill.Id, Assert.Single(resolved.Skills).ImplementationId); - } - - [Fact] - public void Provenance_CoversEffectiveScalarsStructuralContractsAndSkills() - { - var agent = new TypeFerenceCompiler().Validate(Example).Single(x => x.Id == "helio/executive-assistant@1.0.0"); - Assert.Contains(agent.Provenance, x => x.Field == "displayName" && x.Source == agent.Id); - Assert.Contains(agent.Provenance, x => x.Field.StartsWith("satisfies.", StringComparison.Ordinal)); - Assert.All(agent.Skills, skill => - { - Assert.Contains(skill.Provenance, x => x.Field == "skill.capability"); - Assert.Contains(skill.Provenance, x => x.Field == "skill.implementation"); - }); - } - - [Fact] - public void Interfaces_AreSatisfiedImplicitly() - { - var resources = new Dictionary - { - ["test/contract@1.0.0"] = new() { SchemaVersion = 3, Kind = "interface", Id = "test/contract@1.0.0", RequiresSlots = ["owner"] }, - ["test/plain@1.0.0"] = new() { SchemaVersion = 3, Kind = "agent", Id = "test/plain@1.0.0" }, - ["test/owned@1.0.0"] = new() { SchemaVersion = 3, Kind = "agent", Id = "test/owned@1.0.0", Slots = { ["owner"] = "owner.md" } } - }; - var agents = new TypeResolver(resources).ResolveAll(); - Assert.Empty(agents.Single(x => x.Id == "test/plain@1.0.0").Satisfies); - Assert.Contains("test/contract@1.0.0", agents.Single(x => x.Id == "test/owned@1.0.0").Satisfies); - } - - [Fact] - public void InterfaceCapabilityRequirements_MustReferenceCapabilities() - { - var resources = new Dictionary - { - ["test/contract@1.0.0"] = new() - { - SchemaVersion = 3, - Kind = "interface", - Id = "test/contract@1.0.0", - RequiresCapabilities = ["test/capabilities/missing@1.0.0"] - } - }; - - Assert.Contains("Missing capability", Assert.Throws(() => new TypeResolver(resources).ResolveAll()).Message, StringComparison.Ordinal); - } - - [Fact] - public void Skills_MustBindACapability() - { - using var source = new TempDirectory(); - File.WriteAllText(Path.Combine(source.Path, "skill.yaml"), """ -schemaVersion: 3 -kind: skill -id: test/skills/status@1.0.0 -"""); - - Assert.Contains("skills must bind a capability", Assert.Throws(() => new ResourceLoader().Load(source.Path)).Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void AgentSkillBindings_MustMatchTheSkillCapability() - { - var capability = new ResourceDocument { SchemaVersion = 3, Kind = "capability", Id = "test/capabilities/status@1.0.0" }; - var otherCapability = new ResourceDocument { SchemaVersion = 3, Kind = "capability", Id = "test/capabilities/other-status@1.0.0" }; - var skill = new ResourceDocument { SchemaVersion = 3, Kind = "skill", Id = "test/skills/status@1.0.0", Binds = capability.Id }; - var resources = new Dictionary - { - [capability.Id] = capability, - [otherCapability.Id] = otherCapability, - [skill.Id] = skill, - ["test/agent@1.0.0"] = new() - { - SchemaVersion = 3, - Kind = "agent", - Id = "test/agent@1.0.0", - Skills = [new SkillBinding { Ref = skill.Id, Capability = otherCapability.Id }] - } - }; - - Assert.Contains("binding declares capability", Assert.Throws(() => new TypeResolver(resources).ResolveAll()).Message, StringComparison.Ordinal); - } - - [Fact] - public void MissingEmbeddedResources_AreRejected() - { - var resources = new Dictionary - { - ["test/base@1.0.0"] = new() { SchemaVersion = 3, Kind = "agent", Id = "test/base@1.0.0", Embeds = ["test/missing@1.0.0"] } - }; - Assert.Contains("Missing embeddable resource", Assert.Throws(() => new TypeResolver(resources).ResolveAll()).Message, StringComparison.Ordinal); - } - - [Fact] - public void Profiles_CannotEmbedAgents() - { - var resources = new Dictionary - { - ["test/profile@1.0.0"] = new() { SchemaVersion = 3, Kind = "profile", Id = "test/profile@1.0.0", Embeds = ["test/agent@1.0.0"] }, - ["test/agent@1.0.0"] = new() { SchemaVersion = 3, Kind = "agent", Id = "test/agent@1.0.0" } - }; - Assert.Contains("profiles can only embed profiles", Assert.Throws(() => new TypeResolver(resources).ResolveAll()).Message, StringComparison.Ordinal); - } - - [Fact] - public void SkillCapabilityImplementations_MustPreserveSchemas() - { - var capability = new ResourceDocument - { - SchemaVersion = 3, - Kind = "capability", - Id = "test/capabilities/status@1.0.0", - InputSchema = "{\"type\":\"object\",\"properties\":{\"focus\":{\"type\":\"string\"}}}", - OutputSchema = "{\"type\":\"object\"}" - }; - var baseSkill = new ResourceDocument - { - SchemaVersion = 3, - Kind = "skill", - Id = "test/skills/status@1.0.0", - Binds = capability.Id, - InputSchema = "{\"type\":\"object\",\"properties\":{\"focus\":{\"type\":\"string\"}}}", - OutputSchema = "{\"type\":\"object\"}" - }; - var replacement = new ResourceDocument - { - SchemaVersion = 3, - Kind = "skill", - Id = "test/skills/special-status@1.0.0", - Binds = capability.Id, - InputSchema = "{\"type\":\"object\",\"properties\":{\"count\":{\"type\":\"number\"}}}", - OutputSchema = "{\"type\":\"object\"}" - }; - var resources = new Dictionary - { - ["test/base@1.0.0"] = new() - { - SchemaVersion = 3, - Kind = "agent", - Id = "test/base@1.0.0", - Emit = false, - Skills = [new SkillBinding { Ref = baseSkill.Id }] - }, - ["test/concrete@1.0.0"] = new() - { - SchemaVersion = 3, - Kind = "agent", - Id = "test/concrete@1.0.0", - Embeds = ["test/base@1.0.0"], - Skills = [new SkillBinding { Ref = replacement.Id, Capability = capability.Id }] - }, - [capability.Id] = capability, - [baseSkill.Id] = baseSkill, - [replacement.Id] = replacement - }; - Assert.Contains("changes the public contract", Assert.Throws(() => new TypeResolver(resources).ResolveAll()).Message, StringComparison.Ordinal); - } - - [Fact] - public void Interfaces_CanEmbedInterfaces() - { - using var source = new TempDirectory(); - File.WriteAllText(Path.Combine(source.Path, "base.yaml"), "schemaVersion: 3\nkind: interface\nid: test/base@1.0.0\nrequiresSlots: [owner]\n"); - File.WriteAllText(Path.Combine(source.Path, "interface.yaml"), "schemaVersion: 3\nkind: interface\nid: test/contract@1.0.0\nembeds: [test/base@1.0.0]\nrequiresSlots: [repository]\n"); - File.WriteAllText(Path.Combine(source.Path, "agent.yaml"), "schemaVersion: 3\nkind: agent\nid: test/agent@1.0.0\nslots: { owner: owner.md, repository: repository.md }\n"); - File.WriteAllText(Path.Combine(source.Path, "owner.md"), "owner"); - File.WriteAllText(Path.Combine(source.Path, "repository.md"), "repository"); - var agent = new TypeResolver(new ResourceLoader().Load(source.Path)).Resolve("test/agent@1.0.0"); - Assert.Contains("test/contract@1.0.0", agent.Satisfies); - } - - [Theory] - [InlineData("../outside.md", "escapes source root")] - [InlineData("context/missing.md", "does not exist")] - public void ContextPaths_MustStayInsideSourceAndExist(string path, string expected) - { - using var source = new TempDirectory(); - File.WriteAllText(Path.Combine(source.Path, "agent.yaml"), $"schemaVersion: 3\nkind: agent\nid: test/agent@1.0.0\ncontextFiles:\n - {path}\n"); - Assert.Contains(expected, Assert.Throws(() => new ResourceLoader().Load(source.Path)).Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void UnknownYamlFields_AreRejected() - { - using var source = new TempDirectory(); - File.WriteAllText(Path.Combine(source.Path, "root.yaml"), """ -schemaVersion: 3 -kind: agent -id: test/agent@1.0.0 -unknownField: rejected -"""); - Assert.Contains("invalid YAML", Assert.Throws(() => new ResourceLoader().Load(source.Path)).Message, StringComparison.OrdinalIgnoreCase); - } - - [Theory] - [InlineData("extends: test/base@1.0.0")] - [InlineData("implements: [test/contract@1.0.0]")] - public void LegacyInheritanceFields_AreRejected(string field) - { - using var source = new TempDirectory(); - File.WriteAllText(Path.Combine(source.Path, "agent.yaml"), $"schemaVersion: 3\nkind: agent\nid: test/agent@1.0.0\n{field}\n"); - Assert.Contains("invalid YAML", Assert.Throws(() => new ResourceLoader().Load(source.Path)).Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void VersionOneResources_AreRejected() - { - using var source = new TempDirectory(); - File.WriteAllText(Path.Combine(source.Path, "agent.yaml"), "schemaVersion: 1\nkind: agent\nid: test/agent@1.0.0\n"); - Assert.Contains("schemaVersion must be 3", Assert.Throws(() => new ResourceLoader().Load(source.Path)).Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void ResourceIds_RequireSemanticVersions() - { - using var source = new TempDirectory(); - File.WriteAllText(Path.Combine(source.Path, "root.yaml"), """ -schemaVersion: 3 -kind: agent -id: system/object@latest -"""); - Assert.Contains("semantic-version", Assert.Throws(() => new ResourceLoader().Load(source.Path)).Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public async Task McpServer_ListsDottedToolsAndDispatchesInvocationPackage() - { - using var compiled = new TempDirectory(); - new TypeFerenceCompiler().Build(Example, compiled.Path, [CompilationTarget.Neutral]); - var transport = new StdioClientTransport(new StdioClientTransportOptions - { - Name = "TypeFerence test", - Command = "dotnet", - Arguments = [CliAssembly, "serve", Path.Combine(compiled.Path, "neutral")], - WorkingDirectory = Root - }); - await using var client = await McpClient.CreateAsync(transport); - var tools = await client.ListToolsAsync(); - Assert.Contains(tools, x => x.Name == "payments-repo-agent.repository-status"); - var result = await client.CallToolAsync( - "payments-repo-agent.repository-status", - new Dictionary { ["arguments"] = new { focus = "release" } }); - var text = Assert.Single(result.Content.OfType()).Text; - Assert.Contains("payments-repo-agent.repository-status", text, StringComparison.Ordinal); - Assert.Contains("payments-repository-status", text, StringComparison.Ordinal); - } - - [Fact] - public async Task DiffCommand_UsesDocumentedExitCodes() - { - using var compiled = new TempDirectory(); - using var changed = new TempDirectory(); - CopyDirectory(Example, changed.Path); - new TypeFerenceCompiler().Build(changed.Path, compiled.Path, [CompilationTarget.Neutral]); - Assert.Equal(0, await RunCli("diff", changed.Path, "--against", compiled.Path, "--target", "neutral")); - var agentFile = Path.Combine(changed.Path, "agents", "executive-assistant.agent.yaml"); - await File.AppendAllTextAsync(agentFile, "\nworkingNorms:\n - Produce an explicit change for the diff test.\n"); - Assert.Equal(1, await RunCli("diff", changed.Path, "--against", compiled.Path, "--target", "neutral")); - } - - private static string FindRoot() - { - var directory = new DirectoryInfo(AppContext.BaseDirectory); - while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "TypeFerence.slnx"))) directory = directory.Parent; - return directory?.FullName ?? throw new InvalidOperationException("Repository root not found"); - } - - private static async Task RunCli(params string[] arguments) - { - using var process = Process.Start(new ProcessStartInfo("dotnet") - { - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - WorkingDirectory = Root, - ArgumentList = { CliAssembly } - }.WithArguments(arguments)) ?? throw new InvalidOperationException("Could not start TypeFerence CLI"); - await process.WaitForExitAsync(); - return process.ExitCode; - } - - private static string CliAssembly - { - get - { - var configuration = new DirectoryInfo(AppContext.BaseDirectory).Parent?.Name - ?? throw new InvalidOperationException("Could not determine the active build configuration"); - var cli = Path.Combine(Root, "src", "TypeFerence.Cli", "bin", configuration, "net10.0", "typeference.dll"); - return File.Exists(cli) ? cli : throw new InvalidOperationException($"CLI assembly not found for {configuration}: {cli}"); - } - } - - private static void CopyDirectory(string source, string destination) - { - foreach (var directory in Directory.EnumerateDirectories(source, "*", SearchOption.AllDirectories)) - Directory.CreateDirectory(Path.Combine(destination, Path.GetRelativePath(source, directory))); - foreach (var file in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories)) - File.Copy(file, Path.Combine(destination, Path.GetRelativePath(source, file)), true); - } - - private static string TrustConfiguration(string? identityTemplate = null, bool bundleSignatureRequired = false) => $$""" -schemaVersion: 1 -source: - identity: did:web:helio.example:typeference:source:helio - identityType: did - attestations: - - type: https://slsa.dev/provenance/v1 - uri: https://trust.helio.example/provenance/source.intoto.jsonl - digest: sha256:{{new string('a', 64)}} - metadata: - com.helio.governance: - policy: - digest: sha256:{{new string('b', 64)}} - signatureIntent: - algorithm: ES256 - keyRef: did:web:helio.example#catalog-signing -bundles: - identityTemplate: {{identityTemplate ?? "spiffe://helio.example/typeference/{target}/{agent}"}} - identityType: spiffe - trustSchema: - identifier: urn:trust:helio-agent-governance-v1 - version: "1.0" - governanceUri: https://policy.helio.example/governance - verificationMethods: [spiffe, x509] - attestations: - - type: tag:agentrust.io,2026:trace-v0.1 - uri: https://trust.helio.example/runtime-evidence-profile.json - provenance: - - relation: publishedFrom - sourceId: https://github.com/helio/agents - sourceDigest: sha256:{{new string('d', 64)}} - metadata: - com.helio.verification: - policyDigest: sha256:{{new string('c', 64)}} - runtimeEvidenceExpected: true - signatureIntent: - algorithm: ES256 - keyRef: did:web:helio.example#catalog-signing - required: {{bundleSignatureRequired.ToString().ToLowerInvariant()}} -"""; - - private static string SourceTrustConfiguration(string identity, string identityType) => $$""" -schemaVersion: 1 -source: - identity: {{identity}} - identityType: {{identityType}} -"""; - - private sealed class TempDirectory : IDisposable - { - public string Path { get; } = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "typeference-test-" + Guid.NewGuid().ToString("N")); - public TempDirectory() => Directory.CreateDirectory(Path); - public void Dispose() { if (Directory.Exists(Path)) Directory.Delete(Path, true); } - } -} - -internal static class ProcessStartInfoExtensions -{ - public static ProcessStartInfo WithArguments(this ProcessStartInfo info, IEnumerable arguments) - { - foreach (var argument in arguments) info.ArgumentList.Add(argument); - return info; - } -} diff --git a/tests/TypeFerence.Tests/ConformanceSuiteTests.cs b/tests/TypeFerence.Tests/ConformanceSuiteTests.cs deleted file mode 100644 index 2763926..0000000 --- a/tests/TypeFerence.Tests/ConformanceSuiteTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Text.Json; -using TypeFerence.Core; -using Xunit; - -namespace TypeFerence.Tests; - -/// -/// Runs the shared cross-implementation conformance suite (repository -/// conformance/ directory). Digests in the fixture manifests are generated by -/// the Go implementation's runner; this class independently verifies that the -/// reference implementation produces the same bytes. A mismatch on any -/// fixture means the two implementations diverged. -/// -public sealed class ConformanceSuiteTests -{ - private static string FixturesRoot => Path.Combine(FindRoot(), "conformance", "fixtures"); - - public static TheoryData Fixtures() - { - var data = new TheoryData(); - foreach (var dir in Directory.EnumerateDirectories(FixturesRoot).Order(StringComparer.Ordinal)) - data.Add(Path.GetFileName(dir)); - return data; - } - - [Theory] - [MemberData(nameof(Fixtures))] - public void Fixture_MatchesExpectedOutcome(string name) - { - var dir = Path.Combine(FixturesRoot, name); - using var manifest = JsonDocument.Parse(File.ReadAllText(Path.Combine(dir, "manifest.json"))); - var root = manifest.RootElement; - var expect = root.GetProperty("expect").GetString(); - var emitArd = root.TryGetProperty("emitArd", out var domain) ? domain.GetString() : null; - var trustSignatures = root.TryGetProperty("trustSignatures", out var signatures) - ? Path.Combine(dir, signatures.GetString()!) - : null; - var allowUnsigned = root.TryGetProperty("allowUnsignedTrust", out var allow) && allow.GetBoolean(); - - var source = Path.Combine(dir, "source"); - var output = Path.Combine(Path.GetTempPath(), "typeference-conformance-" + Guid.NewGuid().ToString("N")); - try - { - var ard = emitArd is null ? null : new ArdPublicationOptions(emitArd, null, trustSignatures, allowUnsigned); - var targets = new[] { CompilationTarget.Neutral, CompilationTarget.Codex, CompilationTarget.Copilot, CompilationTarget.Cursor }; - - if (expect == "error") - { - Assert.Throws(() => new TypeFerenceCompiler().Build(source, output, targets, ard)); - return; - } - - Assert.Equal("success", expect); - new TypeFerenceCompiler().Build(source, output, targets, ard); - var computed = Directory.EnumerateDirectories(output) - .ToDictionary( - x => Path.GetFileName(x)!, - x => "sha256:" + TypeFerenceCompiler.HashDirectory(x), - StringComparer.Ordinal); - - var expected = root.GetProperty("digests").EnumerateObject() - .ToDictionary(x => x.Name, x => x.Value.GetString()!, StringComparer.Ordinal); - Assert.NotEmpty(expected); - Assert.Equal(expected.Keys.Order(StringComparer.Ordinal), computed.Keys.Order(StringComparer.Ordinal)); - foreach (var pair in expected) - Assert.True(pair.Value == computed[pair.Key], - $"{name}/{pair.Key}: digest mismatch\n got: {computed[pair.Key]}\n want: {pair.Value}"); - } - finally - { - if (Directory.Exists(output)) Directory.Delete(output, true); - } - } - - private static string FindRoot() - { - var directory = new DirectoryInfo(AppContext.BaseDirectory); - while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "TypeFerence.slnx"))) - directory = directory.Parent; - return directory?.FullName ?? throw new InvalidOperationException("Repository root not found"); - } -} diff --git a/tests/TypeFerence.Tests/TypeFerence.Tests.csproj b/tests/TypeFerence.Tests/TypeFerence.Tests.csproj deleted file mode 100644 index 1f98a1d..0000000 --- a/tests/TypeFerence.Tests/TypeFerence.Tests.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - falsetrue - - - - - - - all - -