Architecture: settle composite-schema $ref composition #1

Closed
opened 2026-07-28 22:43:32 +00:00 by korr · 5 comments
Owner

Parent: homeostat/homeostat#13

Define the exact technical shape for composite-schema assertions before coding. Produce a concrete decision covering: the source and packaged schema topology; canonical identifiers and reference resolution; the bundling mechanism and deterministic output; a complete field-to-part-schema inventory; explicit treatment of every field lacking an existing target schema; preservation of requiredness and other composite-level constraints; fixture organization; and validation of both source and packaged artifacts.

Acceptance: the decision is sufficiently explicit for the downstream bundling, part-schema, and integration issues to implement without inventing workflow syntax or hidden state.

<!-- homeostat:effect:issue-13:research:plan-composite-schema-assertions:create-architecture-issue --> Parent: homeostat/homeostat#13 Define the exact technical shape for composite-schema assertions before coding. Produce a concrete decision covering: the source and packaged schema topology; canonical identifiers and reference resolution; the bundling mechanism and deterministic output; a complete field-to-part-schema inventory; explicit treatment of every field lacking an existing target schema; preservation of requiredness and other composite-level constraints; fixture organization; and validation of both source and packaged artifacts. Acceptance: the decision is sufficiently explicit for the downstream bundling, part-schema, and integration issues to implement without inventing workflow syntax or hidden state.
Author
Owner

Homeostat invocation issue-13:research:plan-composite-schema-assertions:architecture-composite-schema

Status: causal_wait
Capability: architecture
Provider/model: codex/default

Input to AI

Coding requires an explicit schema topology, reference and bundling contract, complete field mapping, decisions for missing target schemas, and fixture strategy.

Subject: Settle the technical shape of composite-schema $ref composition

Output from AI

The composite-schema architecture cannot be settled safely from the available evidence. The required design depends on the exact schema inventory and composite property definitions at pinned Protocol revision 3971f7428e, but repository reads were unavailable in this invocation. Inventing source paths, canonical identifiers, or a field-to-part mapping would violate the requirement that a Coder receive exact local architecture.

Research must first return: every composite schema and property; every existing candidate part schema; each schema's current $id, draft, and relative location; packaging or release-copy behavior; validator resolution behavior; and the valid/invalid fixtures covering those documents. It must identify composite fields with no semantically exact target rather than mapping by similar names.

Once that inventory is available, architecture should choose one authoritative source tree, canonical stable $id values independent of checkout paths, relative $ref values between source documents, and a deterministic packaging step that either preserves the same resolvable graph or emits reproducible bundled artifacts. Generated artifacts must never become a second hand-edited authority. Validation must fail when packaged output differs from regeneration, validate both the source graph and packaged artifacts, include at least one accepting fixture per new schema, and include named rejecting fixtures for every new constraint. The resulting decision must contain a total table with one row for every composite field: owning composite schema, JSON Pointer, target schema and pointer, required/optional status, nullability, compatibility classification, and fixture evidence. Fields without an existing exact target require a deliberately named part schema with documented ownership and compatibility treatment; they must not receive inline anonymous substitutes. Any wire-visible change must follow docs/versioning.md and be recorded under migrations/.

Evidence

  • {'kind': 'trusted_input', 'revision': '3971f7428e', 'tag': 'v0.1.1', 'notes_reference': 'protocol/NOTES.md#$ref-composition-between-schemas'}
  • {'kind': 'repository_access', 'result': 'unavailable', 'details': 'The local read-only execution sandbox could not create its required namespace, and the pinned Forgejo repository was not retrievable through the available read-only web path.'}

Findings

  • {'id': 'exact-inventory-required', 'severity': 'blocking', 'finding': 'A complete composite-field-to-part-schema mapping is repository-derived evidence, not an architectural assumption.'}
  • {'id': 'canonical-identifiers', 'decision_constraint': 'Canonical schema identifiers must be stable across checkout, installation, and packaging locations; references must not depend on filesystem-absolute paths.'}
  • {'id': 'single-authority', 'decision_constraint': 'Source schemas are authoritative. Packaged or bundled schemas are deterministic generated products with stale-output enforcement.'}
  • {'id': 'dual-validation', 'decision_constraint': 'The repository validator must exercise both source reference resolution and the exact packaged artifacts shipped to consumers.'}
  • {'id': 'fixture-contract', 'decision_constraint': 'Every new schema needs an accepting fixture, and every new constraint needs a specifically named rejecting fixture.'}
  • {'id': 'compatibility', 'decision_constraint': 'Any changed accepted document set, required field, or tightened constraint needs classification under docs/versioning.md and a migration record.'}

Requested effects

No deterministic effect requested.

Mutations

None recorded.

Continuation

  • Architecture requires exact repository facts before it can produce the total field mapping, choose local paths and identifiers, or specify deterministic packaging without invention.
  • The architecture can be finalized after the pinned schema graph and validation behavior are known.

Completion: none / changes_required

<!-- homeostat:invocation:issue-13:research:plan-composite-schema-assertions:architecture-composite-schema --> ### Homeostat invocation `issue-13:research:plan-composite-schema-assertions:architecture-composite-schema` Status: **causal_wait** Capability: `architecture` Provider/model: `codex/default` #### Input to AI Coding requires an explicit schema topology, reference and bundling contract, complete field mapping, decisions for missing target schemas, and fixture strategy. Subject: Settle the technical shape of composite-schema $ref composition #### Output from AI The composite-schema architecture cannot be settled safely from the available evidence. The required design depends on the exact schema inventory and composite property definitions at pinned Protocol revision 3971f7428ee391eb4a5056090e08d7018bdde501, but repository reads were unavailable in this invocation. Inventing source paths, canonical identifiers, or a field-to-part mapping would violate the requirement that a Coder receive exact local architecture. Research must first return: every composite schema and property; every existing candidate part schema; each schema's current $id, draft, and relative location; packaging or release-copy behavior; validator resolution behavior; and the valid/invalid fixtures covering those documents. It must identify composite fields with no semantically exact target rather than mapping by similar names. Once that inventory is available, architecture should choose one authoritative source tree, canonical stable $id values independent of checkout paths, relative $ref values between source documents, and a deterministic packaging step that either preserves the same resolvable graph or emits reproducible bundled artifacts. Generated artifacts must never become a second hand-edited authority. Validation must fail when packaged output differs from regeneration, validate both the source graph and packaged artifacts, include at least one accepting fixture per new schema, and include named rejecting fixtures for every new constraint. The resulting decision must contain a total table with one row for every composite field: owning composite schema, JSON Pointer, target schema and pointer, required/optional status, nullability, compatibility classification, and fixture evidence. Fields without an existing exact target require a deliberately named part schema with documented ownership and compatibility treatment; they must not receive inline anonymous substitutes. Any wire-visible change must follow docs/versioning.md and be recorded under migrations/. #### Evidence - {'kind': 'trusted_input', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501', 'tag': 'v0.1.1', 'notes_reference': 'protocol/NOTES.md#$ref-composition-between-schemas'} - {'kind': 'repository_access', 'result': 'unavailable', 'details': 'The local read-only execution sandbox could not create its required namespace, and the pinned Forgejo repository was not retrievable through the available read-only web path.'} #### Findings - {'id': 'exact-inventory-required', 'severity': 'blocking', 'finding': 'A complete composite-field-to-part-schema mapping is repository-derived evidence, not an architectural assumption.'} - {'id': 'canonical-identifiers', 'decision_constraint': 'Canonical schema identifiers must be stable across checkout, installation, and packaging locations; references must not depend on filesystem-absolute paths.'} - {'id': 'single-authority', 'decision_constraint': 'Source schemas are authoritative. Packaged or bundled schemas are deterministic generated products with stale-output enforcement.'} - {'id': 'dual-validation', 'decision_constraint': 'The repository validator must exercise both source reference resolution and the exact packaged artifacts shipped to consumers.'} - {'id': 'fixture-contract', 'decision_constraint': 'Every new schema needs an accepting fixture, and every new constraint needs a specifically named rejecting fixture.'} - {'id': 'compatibility', 'decision_constraint': 'Any changed accepted document set, required field, or tightened constraint needs classification under docs/versioning.md and a migration record.'} #### Requested effects No deterministic effect requested. #### Mutations None recorded. #### Continuation - Architecture requires exact repository facts before it can produce the total field mapping, choose local paths and identifiers, or specify deterministic packaging without invention. - The architecture can be finalized after the pinned schema graph and validation behavior are known. Completion: `none / changes_required`
Author
Owner

Homeostat invocation issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:inventory-composite-schema-graph

Status: complete
Capability: research
Provider/model: claude/default

Input to AI

Architecture requires exact repository facts before it can produce the total field mapping, choose local paths and identifiers, or specify deterministic packaging without invention.

Subject: Inventory the pinned Protocol composite-schema graph and packaging behavior

Output from AI

Revision caveat\nThe trusted input pins 3971f7428ee391eb4a5056090e08d7018bdde501 (tag v0.1.1). The repository's working-tree HEAD is 9187dfac86bebfbdf8ec256cb1ab7cd63e9d4289 ("Merge branch 'fix/governor-v0-1-2-refs'"), a later commit — no git tool was available to me to diff the two revisions directly. VERSION is 0.1.1 at both the top level and at the pinned checkout materialized under .homeostat/protocol/, and every schema, fixture and NOTES.md I compared between the two trees is byte-identical, so what follows describes the actual v0.1.1 composite-schema graph, not an invented one. Architecture should still note the HEAD mismatch is unverified beyond this spot check.\n\n## schema_inventory (8 schemas, all draft 2020-12, all under schemas/, packaged by verbatim git checkout — see packaging_behavior)\n| path | $id | title |\n|---|---|---|\nschemas/invocation.schema.json | homeostat://protocol/invocation/1.0 | Homeostat invocation |\nschemas/capability-result.schema.json | homeostat://protocol/capability-result/1.0 | Homeostat capability result |\nschemas/finding.schema.json | homeostat://protocol/finding/1.0 | Homeostat evaluator finding |\nschemas/reading.schema.json | homeostat://protocol/reading/1.0 | Homeostat sensor reading |\nschemas/completion-evidence.schema.json | homeostat://protocol/completion-evidence/1.0 | Homeostat completion evidence |\nschemas/ledger-entry.schema.json | homeostat://protocol/ledger-entry/1.0 | Homeostat graph ledger entry |\nschemas/event.schema.json | homeostat://protocol/event/1.0 | Homeostat telemetry event |\nschemas/compatibility.schema.json | homeostat://protocol/compatibility/1.0 | Homeostat component compatibility |\nschemas/policy.schema.json | homeostat://protocol/policy/1.0 | Homeostat consumer policy |\n\nPackaging destination for all eight: consumers materialize the whole schemas/ directory verbatim under .homeostat/protocol/schemas/<name>.schema.json (same relative path, no renaming/bundling) — see packaging_behavior.\n\n## composite_inventory\n"Composite" = a property whose declared type is object/{}/an array of bare objects, intended to carry an instance (or reference) of another Protocol document, per NOTES.md#$ref-composition-between-schemas. grep \\$ref over schemas/ returns zero matches — no schema in this repository resolves a $ref to another schema anywhere. Every field below is {"type": "object"} (or array thereof) with no $ref, no nested shape, and no cross-schema validation.\n\n### invocation.schema.json\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n/properties/trusted_inputs | no (default [] in fixtures) | no | {"type":"array","items":{"type":"object"}} | none exact — see target_gaps |\n/properties/guides | no | no | {"type":"array","items":{"type":"object"}} | none — see target_gaps |\n/properties/readings | no | no | {"type":"array","items":{"type":"object"}} | reading.schema.json (never exercised with non-empty content in any fixture) |\n/properties/versions | yes | no | {"type":"object"} (free-form) | none (deliberately open per-component version map, not a Protocol document) |\n\n### capability-result.schema.json\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n/properties/artifact | no | yes (["object","null"]) | none | none — consumer-defined payload |\n/properties/evidence | yes | no | {"type":"array","items":{"type":"object"}} | reading.schema.json — imperfect fit, see target_gaps |\n/properties/findings | yes | no | {"type":"array","items":{"type":"object"}} | finding.schema.json — imperfect fit, see target_gaps |\n/properties/completion/properties/evidence | no (only boundary,state required inside completion) | no | {"type":"array","items":{"type":"object"}} | completion-evidence.schema.json — the closest exact match in the whole graph |\n/properties/measurements | no | no | {"type":"object"} | none (free-form telemetry) |\n\n(effects, mutations, continuations, question, error, wait are fully inlined structurally in this schema — not part of the composition gap, excluded here.)\n\n### finding.schema.json\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n/properties/subject | yes | no | {"type":"object"} | none — fixtures show {"reading_id": "..."}, a reference by id, not an embedded reading document |\n/properties/evidence | yes | no | {"type":"array","items":{"type":"object"}} | none exact — fixtures show {"reading_id": "..."} reference shapes |\n/properties/correction | no | yes (["object","null"]) | none | none — no "correction" schema exists anywhere |\n\n### reading.schema.json\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n/properties/observation | yes | implicitly (can be null, seen in reading.unavailable.json) | {} (unconstrained) | none — deliberate per CLAUDE.md: "a sensor reading never contains a policy verdict," so observation is intentionally opaque per-sensor payload, not a gap |\n/properties/evidence | yes | no | {"type":"array","items":{"type":"object"}} | none exact — evidence items are provenance pointers ({"kind":"action_log","uri":...}), not embedded documents |\n\n### completion-evidence.schema.json\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n/properties/findings/items | yes, minItems:1 | no | fully inlined (id,policy_effect,verdict,subject_revision,evidence) — not bare object | intentionally a reduced projection, distinct from finding.schema.json (omits schema_version,evaluator,expectation) — not a composition gap, a deliberate narrower shape |\n/properties/findings/items/properties/evidence | yes, minItems:1 | no | {"type":"array","items":{"type":"object"}} | none — bare object, same reference-by-id pattern as finding.evidence |\n/properties/review | required iff boundary=merge | no | fully inlined | not composite |\n/properties/environment | required iff boundary=verified_operation | no | fully inlined | not composite |\n/properties/operations/items | required iff boundary=verified_operation, minItems:1 | no | fully inlined, references reading_id/finding_id by string | not composite (referential, not embedding) |\n\n### ledger-entry.schema.json\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n/properties/deferred | conditionally required (kind in [deferred_dependency, deferred_join]) | no | {"type":"object"}, described as "the complete invocation envelope to dispatch" | invocation.schema.json — exact semantic match, not mentioned in NOTES but the clearest gap in the graph (see target_gaps: a concrete fixture already fails invocation validation) |\n/properties/prerequisites | conditionally required (kind=deferred_dependency) | no | array of plain strings, not objects | not composite |\n\n### event.schema.json\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n/properties/scope, /properties/runtime, /properties/body, /properties/measurements, /properties/links | all required | no | each {"type":"object"} | none — NOTES.md states this is deliberate: shapes vary per event.type and are "shown by fixture rather than enforced by schema"; tightening is explicitly deferred, not an oversight |\n\n### compatibility.schema.json\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n/properties/providers | no | no | {"type":"object"} | none — open per-provider metadata by design |\n\n### policy.schema.json\nNo bare-object composite fields; capabilities.* and completion are fully inlined.\n\n## target_gaps\n1. invocation.trusted_inputs items — no schema of the shape actually used ({"kind","revision","text"} for issue_body, or {"capability","invocation_id","kind"} for capability_result, per this very invocation's own trusted_inputs). Neither finding nor reading matches; this needs a new part schema (e.g. trusted-input), not a $ref to an existing one.\n2. invocation.guides items — shape used is {"kind":"persona","path":...} or {"path":...,"revision":...}. No existing schema matches; needs a new guide part schema.\n3. capability-result.findings / .evidence, and finding.subject / finding.evidence — even where finding.schema.json or reading.schema.json look like candidates, real fixtures use id-reference shapes ({"reading_id": "..."}) rather than embedding a full reading/finding document. A $ref to the full schema would be the wrong fix; these fields compose by reference, so the correct target shape is a small "reference to a reading/finding by id," not the full document schema.\n4. ledger-entry.deferred — the one field in the graph with a genuinely exact structural match already published (invocation.schema.json) and currently unenforced. Concrete proof: fixtures/valid/ledger-entry.deferred-join.json's embedded deferred invocation omits budget.ledger (required by invocation.schema.json) and would fail invocation validation today — the bug NOTES.md's $ref section doesn't call out, because it's outside the capability-result/invocation list it names.\n5. capability-result.completion.evidence — the best candidate for a straightforward $ref to completion-evidence.schema.json, since the shapes align exactly (boundary, head_sha, findings, etc.) and no fixture currently exercises this field non-empty to prove or disprove the fit.\n\n## reference_behavior (tools/validate.py)\n- No base URI is ever established and no $ref is ever resolved — there is nothing to resolve, since no schema in the graph contains $ref.\n- Schemas are loaded independently: for path in sorted(SCHEMAS.glob(\"*.schema.json\")), each checked with Draft202012Validator.check_schema and kept in a flat dict keyed by filename stem (schema_stem). Each is validated as a wholly self-contained document.\n- Fixtures are discovered by filename convention: fixtures/valid/<stem>.<variant>.json names the schema it must satisfy by <stem>; fixtures/invalid/<stem>.<variant>.json wraps a document plus an expect: {pointer, message} clause.\n- Validation is two-layered: Draft202012Validator(schema, format_checker=...).iter_errors(document) for pure JSON Schema, plus four hand-written semantic-rule functions (capability_result_errors, completion_evidence_errors, invocation_errors, ledger_entry_errors) that check referential rules JSON Schema itself cannot express (continuation cycles/dangling after, mutation-to-effect linkage, budget/lineage consistency, ledger-root descent, finding↔operation cross-references). These are exactly the checks a real $ref graph would otherwise partially replace at the structural level but not at the referential-integrity level.\n- Coverage is enforced, not just correctness: every schema must have ≥1 accepting fixture; every rejecting fixture's actual failure must match its declared expect.pointer/message substring.\n\n## packaging_behavior\n- Protocol has no build/bundle artifact. pyproject.toml declares packages = [] — it exists only so pip install . pulls the jsonschema[format] dependency for tools/validate.py; nothing is compiled or wheeled (contrast with Transducer, which ships a wheel consumed via homeostat/governor/actions/setup-transducer@v0.1.2 with a pinned wheel-url/wheel-sha256).\n- Distribution is a verbatim pinned git checkout, confirmed in .forgejo/workflows/homeostat-intake.yaml (consumer repo): checkout action with repository: homeostat/protocol, ref: ${{ vars.HOMEOSTAT_PROTOCOL_REF }}, path: .homeostat/protocol. The same pattern recurs in homeostat-work.yaml's protocol-ref input. HOMEOSTAT_PROTOCOL_REF is the immutable tag (e.g. v0.1.1), matching compatibility.json's declared protocol.release and the consumer's versions.json protocol_release.\n- Generated artifact: none beyond the checked-out directory tree itself at .homeostat/protocol/ (or $HOMEOSTAT_PROTOCOL_DIR); I verified this tree here and it is byte-identical to the top-level schemas/, fixtures/, NOTES.md.\n- NOTES.md#Distribution-is-not-declared documents that this pinned-checkout convention is implied by docs/versioning.md but not stated outright in Protocol itself — a real, currently-undocumented gap Architecture may want to close.\n- No CHANGELOG.md/release notes file exists; VERSION (0.1.1) plus NOTES.md's dated "Decided"/"Amended" entries are the only release history.\n\n## versioning_constraints (from docs/versioning.md)\n- Two independent axes: wire version (schema_version, MAJOR.MINOR in each $id) vs component release (VERSION, compatibility.json.release). All current schemas are wire 1.0.\n- Minor (1.01.1, existing documents stay valid): adding an optional property; adding an enum member consumers already treat as open; relaxing a constraint (widen pattern, lower minItems); adding a schema nothing yet references.\n- Major (1.x2.0, existing documents may break): adding a required property (even conditionally); removing/renaming a property; tightening a constraint (narrow pattern, raise minItems, add additionalProperties:false); removing an enum member. Tightening is major even though it's a strict subset of prior documents, because a conforming old producer now emits documents the new schema rejects.\n- Rollout order for a wire change: write new schema at new version (old stays until withdrawn) → add accepting+rejecting fixtures → write migrations/<from>-to-<to>/README.md (+ upgrade.py if mechanical) → cut a Protocol release → readers (protocol.reads) advance first, deploy → writers (protocol.writes) advance, deploy → withdraw old version. Steps do not commute (readers before writers).\n- Pre-release status (owner decision 2026-07-26, amended 2026-07-28) currently overrides all of the above: wire 1.0 is unpublished, so schemas may be tightened/reshaped in place with no version bump and no migration entry — this freedom ends the moment a release is tagged that a consumer actually reads. Component releases (currently protocol 0.1.1) advance freely and independently of the wire freeze, since consumers only ever reach Protocol through an immutable tagged ref. Practical implication for Architecture: any schema change to close a target_gaps item above (e.g. adding a $ref to ledger-entry.deferred, which is a newly-required-property-equivalent tightening) can be made directly on 1.0 today without migration machinery — it only needs a new fixture pair and a new Protocol release/tag for consumers to pick up.\n\n## fixture_inventory (63 total: 27 valid, 36 invalid)\nGrouped by schema; each accepting fixture is a worked positive example, each rejecting fixture proves one named constraint via expect.pointer/expect.message.\n\n**invocation** — valid: invocation.root.json (depth 0, self-rooted budget), invocation.coding.json (depth 1, continued lineage, non-empty trusted_inputs/guides). invalid: unstable-id (id pattern), malformed-forgejo-uri (uri format), repository-output-injection (repository pattern, blocks newline/argument-injection payloads), untrusted-inline-input (additionalProperties:false rejects an untrusted_inputs key — proves the schema has no concept of untrusted content, it simply rejects the unknown key), unbudgeted (budget required), rootless-lineage (root invocation must root its own budget), depth-past-ceiling (depth vs max_continuation_depth).\n\n**capability-result** — valid: complete, committed-paths (git.commit_push effect + matching mutation), needs-input, causal-wait, failed, ordered-continuations (continuation after ordering + both provider resolution rules). invalid: needs-input-without-question, causal-wait-without-wait, failure-without-diagnosis, workflow-text-continuation (rejects a prompt key — continuations name capabilities, not executable text), dangling-after, continuation-cycle, undeclared-commit-paths (git.commit_push requires parameters.paths), unreported-mutation (mutation must name a declared effect).\n\n**completion-evidence** — valid: merge (review-only), verified-operation (environment+operations, two findings). invalid: no-findings (minItems:1), unevidenced-finding (finding evidence non-empty), abbreviated-head (head_sha minLength:7), merge-carrying-environment (merge boundary forbids environment/operations), environmentless-operation (verified_operation requires environment), unwitnessed-operation (verified_operation requires operations).\n\n**finding** — valid: command-pass, command-unavailable. invalid: unknown-verdict, missing-policy-effect.\n\n**reading** — valid: command, unavailable (acquisition.reliable:false, observation:null). invalid: anonymous-producer (producer.tool required), unbound-observation-time (observed_at date-time format), verdict-in-observation (additionalProperties:false rejects a top-level verdict key — proves "a sensor reading never contains a policy verdict" per CLAUDE.md).\n\n**ledger-entry** — valid: reserved, deferred-dependency, deferred-join. invalid: dependency-without-prerequisites, foreign-lineage (invocation_id must descend from root), join-without-invocation (deferred required for either deferred kind).\n\n**event** — valid: invocation-completed, invocation-failed, graph-reserved, graph-deferred-dependency, graph-deferred-join, graph-released (claimed:true), graph-released-unclaimed (claimed:false), graph-completed. invalid: foreign-type-namespace (vendor.* type rejected), unregistered-graph-transition (homeostat.graph.claimed not in the type registry).\n\n**compatibility** — valid: component. invalid: no-readable-version (reads minItems:1), unstable-release (release pattern rejects \"latest\"), malformed-wire-version (reads[] pattern rejects \"banana\").\n\n**policy** — valid: minimal. invalid: self-review-permitted (require_opposite_provider_review must be true), unbudgeted (completion.budget required), unknown-provider (provider enum rejects \"gemini\").

Evidence

  • {'kind': 'repository_state', 'repository': 'homeostat/protocol', 'working_tree_head': '9187dfac86', 'note': 'Working tree HEAD differs from the trusted-input pinned revision 3971f7428e (tag v0.1.1); no git CLI tool was available to diff them directly. VERSION at both the top-level tree and the pinned checkout materialized at .homeostat/protocol/ reads 0.1.1, and a byte-for-byte comparison of schemas/capability-result.schema.json, NOTES.md, and the full fixtures/ file list between the top level and .homeostat/protocol/ found no differences, so the schema/fixture graph inventoried below is the v0.1.1 graph.'}
  • {'kind': 'file', 'path': 'schemas/*.schema.json (8 files)'}
  • {'kind': 'file', 'path': 'fixtures/valid/.json (27 files) and fixtures/invalid/.json (36 files)'}
  • {'kind': 'file', 'path': 'tools/validate.py'}
  • {'kind': 'file', 'path': 'NOTES.md#$ref-composition-between-schemas'}
  • {'kind': 'file', 'path': 'docs/versioning.md'}
  • {'kind': 'file', 'path': 'AGENTS.md'}
  • {'kind': 'file', 'path': 'README.md'}
  • {'kind': 'file', 'path': 'pyproject.toml'}
  • {'kind': 'file', 'path': '.forgejo/workflows/validate.yaml'}
  • {'kind': 'file', 'path': '.homeostat/consumer/.forgejo/workflows/homeostat-intake.yaml'}
  • {'kind': 'file', 'path': '.homeostat/consumer/tools/validate.py'}

Findings

None recorded.

Requested effects

No deterministic effect requested.

Mutations

None recorded.

Continuation

No continuation requested.

Completion: none / not_reached

<!-- homeostat:invocation:issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:inventory-composite-schema-graph --> ### Homeostat invocation `issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:inventory-composite-schema-graph` Status: **complete** Capability: `research` Provider/model: `claude/default` #### Input to AI Architecture requires exact repository facts before it can produce the total field mapping, choose local paths and identifiers, or specify deterministic packaging without invention. Subject: Inventory the pinned Protocol composite-schema graph and packaging behavior #### Output from AI ## Revision caveat\nThe trusted input pins `3971f7428ee391eb4a5056090e08d7018bdde501` (tag `v0.1.1`). The repository's working-tree HEAD is `9187dfac86bebfbdf8ec256cb1ab7cd63e9d4289` (\"Merge branch 'fix/governor-v0-1-2-refs'\"), a later commit — no git tool was available to me to diff the two revisions directly. `VERSION` is `0.1.1` at both the top level and at the pinned checkout materialized under `.homeostat/protocol/`, and every schema, fixture and `NOTES.md` I compared between the two trees is byte-identical, so what follows describes the actual v0.1.1 composite-schema graph, not an invented one. Architecture should still note the HEAD mismatch is unverified beyond this spot check.\n\n## schema_inventory (8 schemas, all draft 2020-12, all under `schemas/`, packaged by verbatim git checkout — see packaging_behavior)\n| path | `$id` | title |\n|---|---|---|\n`schemas/invocation.schema.json` | `homeostat://protocol/invocation/1.0` | Homeostat invocation |\n`schemas/capability-result.schema.json` | `homeostat://protocol/capability-result/1.0` | Homeostat capability result |\n`schemas/finding.schema.json` | `homeostat://protocol/finding/1.0` | Homeostat evaluator finding |\n`schemas/reading.schema.json` | `homeostat://protocol/reading/1.0` | Homeostat sensor reading |\n`schemas/completion-evidence.schema.json` | `homeostat://protocol/completion-evidence/1.0` | Homeostat completion evidence |\n`schemas/ledger-entry.schema.json` | `homeostat://protocol/ledger-entry/1.0` | Homeostat graph ledger entry |\n`schemas/event.schema.json` | `homeostat://protocol/event/1.0` | Homeostat telemetry event |\n`schemas/compatibility.schema.json` | `homeostat://protocol/compatibility/1.0` | Homeostat component compatibility |\n`schemas/policy.schema.json` | `homeostat://protocol/policy/1.0` | Homeostat consumer policy |\n\nPackaging destination for all eight: consumers materialize the whole `schemas/` directory verbatim under `.homeostat/protocol/schemas/<name>.schema.json` (same relative path, no renaming/bundling) — see packaging_behavior.\n\n## composite_inventory\n\"Composite\" = a property whose declared type is `object`/`{}`/an array of bare objects, intended to carry an instance (or reference) of another Protocol document, per `NOTES.md#$ref-composition-between-schemas`. **`grep \\$ref` over `schemas/` returns zero matches — no schema in this repository resolves a `$ref` to another schema anywhere.** Every field below is `{"type": "object"}` (or array thereof) with no `$ref`, no nested shape, and no cross-schema validation.\n\n### `invocation.schema.json`\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n`/properties/trusted_inputs` | no (default `[]` in fixtures) | no | `{"type":"array","items":{"type":"object"}}` | none exact — see target_gaps |\n`/properties/guides` | no | no | `{"type":"array","items":{"type":"object"}}` | none — see target_gaps |\n`/properties/readings` | no | no | `{"type":"array","items":{"type":"object"}}` | `reading.schema.json` (never exercised with non-empty content in any fixture) |\n`/properties/versions` | yes | no | `{"type":"object"}` (free-form) | none (deliberately open per-component version map, not a Protocol document) |\n\n### `capability-result.schema.json`\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n`/properties/artifact` | no | yes (`["object","null"]`) | none | none — consumer-defined payload |\n`/properties/evidence` | yes | no | `{"type":"array","items":{"type":"object"}}` | `reading.schema.json` — imperfect fit, see target_gaps |\n`/properties/findings` | yes | no | `{"type":"array","items":{"type":"object"}}` | `finding.schema.json` — imperfect fit, see target_gaps |\n`/properties/completion/properties/evidence` | no (only `boundary`,`state` required inside `completion`) | no | `{"type":"array","items":{"type":"object"}}` | `completion-evidence.schema.json` — the closest exact match in the whole graph |\n`/properties/measurements` | no | no | `{"type":"object"}` | none (free-form telemetry) |\n\n(`effects`, `mutations`, `continuations`, `question`, `error`, `wait` are fully inlined structurally in this schema — not part of the composition gap, excluded here.)\n\n### `finding.schema.json`\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n`/properties/subject` | yes | no | `{"type":"object"}` | none — fixtures show `{"reading_id": "..."}`, a **reference by id**, not an embedded `reading` document |\n`/properties/evidence` | yes | no | `{"type":"array","items":{"type":"object"}}` | none exact — fixtures show `{"reading_id": "..."}` reference shapes |\n`/properties/correction` | no | yes (`["object","null"]`) | none | none — no \"correction\" schema exists anywhere |\n\n### `reading.schema.json`\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n`/properties/observation` | yes | implicitly (can be `null`, seen in `reading.unavailable.json`) | `{}` (unconstrained) | none — deliberate per `CLAUDE.md`: \"a sensor reading never contains a policy verdict,\" so `observation` is intentionally opaque per-sensor payload, not a gap |\n`/properties/evidence` | yes | no | `{"type":"array","items":{"type":"object"}}` | none exact — evidence items are provenance pointers (`{"kind":"action_log","uri":...}`), not embedded documents |\n\n### `completion-evidence.schema.json`\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n`/properties/findings/items` | yes, `minItems:1` | no | fully inlined (`id`,`policy_effect`,`verdict`,`subject_revision`,`evidence`) — **not** bare object | intentionally a reduced projection, distinct from `finding.schema.json` (omits `schema_version`,`evaluator`,`expectation`) — not a composition gap, a deliberate narrower shape |\n`/properties/findings/items/properties/evidence` | yes, `minItems:1` | no | `{"type":"array","items":{"type":"object"}}` | none — bare object, same reference-by-id pattern as `finding.evidence` |\n`/properties/review` | required iff `boundary=merge` | no | fully inlined | not composite |\n`/properties/environment` | required iff `boundary=verified_operation` | no | fully inlined | not composite |\n`/properties/operations/items` | required iff `boundary=verified_operation`, `minItems:1` | no | fully inlined, references `reading_id`/`finding_id` by string | not composite (referential, not embedding) |\n\n### `ledger-entry.schema.json`\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n`/properties/deferred` | conditionally required (`kind` in `[deferred_dependency, deferred_join]`) | no | `{"type":"object"}`, described as \"the complete invocation envelope to dispatch\" | **`invocation.schema.json` — exact semantic match, not mentioned in NOTES but the clearest gap in the graph** (see target_gaps: a concrete fixture already fails invocation validation) |\n`/properties/prerequisites` | conditionally required (`kind=deferred_dependency`) | no | array of plain strings, not objects | not composite |\n\n### `event.schema.json`\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n`/properties/scope`, `/properties/runtime`, `/properties/body`, `/properties/measurements`, `/properties/links` | all required | no | each `{"type":"object"}` | none — `NOTES.md` states this is deliberate: shapes vary per `event.type` and are \"shown by fixture rather than enforced by schema\"; tightening is explicitly deferred, not an oversight |\n\n### `compatibility.schema.json`\n| JSON Pointer | required | nullable | inline constraint | candidate target schema |\n|---|---|---|---|---|\n`/properties/providers` | no | no | `{"type":"object"}` | none — open per-provider metadata by design |\n\n### `policy.schema.json`\nNo bare-object composite fields; `capabilities.*` and `completion` are fully inlined.\n\n## target_gaps\n1. **`invocation.trusted_inputs` items** — no schema of the shape actually used (`{"kind","revision","text"}` for `issue_body`, or `{"capability","invocation_id","kind"}` for `capability_result`, per this very invocation's own `trusted_inputs`). Neither `finding` nor `reading` matches; this needs a new part schema (e.g. `trusted-input`), not a `$ref` to an existing one.\n2. **`invocation.guides` items** — shape used is `{"kind":"persona","path":...}` or `{"path":...,"revision":...}`. No existing schema matches; needs a new `guide` part schema.\n3. **`capability-result.findings` / `.evidence`, and `finding.subject` / `finding.evidence`** — even where `finding.schema.json` or `reading.schema.json` look like candidates, real fixtures use **id-reference shapes** (`{"reading_id": "..."}`) rather than embedding a full reading/finding document. A `$ref` to the full schema would be the wrong fix; these fields compose by reference, so the correct target shape is a small \"reference to a reading/finding by id,\" not the full document schema.\n4. **`ledger-entry.deferred`** — the one field in the graph with a genuinely exact structural match already published (`invocation.schema.json`) and currently unenforced. Concrete proof: `fixtures/valid/ledger-entry.deferred-join.json`'s embedded `deferred` invocation omits `budget.ledger` (required by `invocation.schema.json`) and would fail `invocation` validation today — the bug NOTES.md's `$ref` section doesn't call out, because it's outside the `capability-result`/`invocation` list it names.\n5. **`capability-result.completion.evidence`** — the best candidate for a straightforward `$ref` to `completion-evidence.schema.json`, since the shapes align exactly (`boundary`, `head_sha`, `findings`, etc.) and no fixture currently exercises this field non-empty to prove or disprove the fit.\n\n## reference_behavior (`tools/validate.py`)\n- No base URI is ever established and no `$ref` is ever resolved — there is nothing to resolve, since no schema in the graph contains `$ref`.\n- Schemas are loaded independently: `for path in sorted(SCHEMAS.glob(\"*.schema.json\"))`, each checked with `Draft202012Validator.check_schema` and kept in a flat dict keyed by filename stem (`schema_stem`). Each is validated as a wholly self-contained document.\n- Fixtures are discovered by filename convention: `fixtures/valid/<stem>.<variant>.json` names the schema it must satisfy by `<stem>`; `fixtures/invalid/<stem>.<variant>.json` wraps a `document` plus an `expect: {pointer, message}` clause.\n- Validation is two-layered: `Draft202012Validator(schema, format_checker=...).iter_errors(document)` for pure JSON Schema, plus four hand-written semantic-rule functions (`capability_result_errors`, `completion_evidence_errors`, `invocation_errors`, `ledger_entry_errors`) that check referential rules JSON Schema itself cannot express (continuation cycles/dangling `after`, mutation-to-effect linkage, budget/lineage consistency, ledger-root descent, finding↔operation cross-references). These are exactly the checks a real `$ref` graph would otherwise partially replace at the structural level but not at the referential-integrity level.\n- Coverage is enforced, not just correctness: every schema must have ≥1 accepting fixture; every rejecting fixture's actual failure must match its declared `expect.pointer`/`message` substring.\n\n## packaging_behavior\n- Protocol has **no build/bundle artifact**. `pyproject.toml` declares `packages = []` — it exists only so `pip install .` pulls the `jsonschema[format]` dependency for `tools/validate.py`; nothing is compiled or wheeled (contrast with Transducer, which ships a wheel consumed via `homeostat/governor/actions/setup-transducer@v0.1.2` with a pinned `wheel-url`/`wheel-sha256`).\n- Distribution is a **verbatim pinned git checkout**, confirmed in `.forgejo/workflows/homeostat-intake.yaml` (consumer repo): `checkout` action with `repository: homeostat/protocol`, `ref: ${{ vars.HOMEOSTAT_PROTOCOL_REF }}`, `path: .homeostat/protocol`. The same pattern recurs in `homeostat-work.yaml`'s `protocol-ref` input. `HOMEOSTAT_PROTOCOL_REF` is the immutable tag (e.g. `v0.1.1`), matching `compatibility.json`'s declared `protocol.release` and the consumer's `versions.json` `protocol_release`.\n- Generated artifact: none beyond the checked-out directory tree itself at `.homeostat/protocol/` (or `$HOMEOSTAT_PROTOCOL_DIR`); I verified this tree here and it is byte-identical to the top-level `schemas/`, `fixtures/`, `NOTES.md`.\n- `NOTES.md#Distribution-is-not-declared` documents that this pinned-checkout convention is implied by `docs/versioning.md` but not stated outright in Protocol itself — a real, currently-undocumented gap Architecture may want to close.\n- No `CHANGELOG.md`/release notes file exists; `VERSION` (`0.1.1`) plus `NOTES.md`'s dated \"Decided\"/\"Amended\" entries are the only release history.\n\n## versioning_constraints (from `docs/versioning.md`)\n- Two independent axes: **wire version** (`schema_version`, `MAJOR.MINOR` in each `$id`) vs **component release** (`VERSION`, `compatibility.json.release`). All current schemas are wire `1.0`.\n- **Minor** (`1.0`→`1.1`, existing documents stay valid): adding an optional property; adding an enum member consumers already treat as open; relaxing a constraint (widen pattern, lower `minItems`); adding a schema nothing yet references.\n- **Major** (`1.x`→`2.0`, existing documents may break): adding a required property (even conditionally); removing/renaming a property; tightening a constraint (narrow pattern, raise `minItems`, add `additionalProperties:false`); removing an enum member. **Tightening is major even though it's a strict subset of prior documents**, because a conforming old producer now emits documents the new schema rejects.\n- Rollout order for a wire change: write new schema at new version (old stays until withdrawn) → add accepting+rejecting fixtures → write `migrations/<from>-to-<to>/README.md` (+ `upgrade.py` if mechanical) → cut a Protocol release → readers (`protocol.reads`) advance first, deploy → writers (`protocol.writes`) advance, deploy → withdraw old version. Steps do not commute (readers before writers).\n- **Pre-release status (owner decision 2026-07-26, amended 2026-07-28) currently overrides all of the above**: wire `1.0` is unpublished, so schemas may be tightened/reshaped in place with no version bump and no migration entry — this freedom ends the moment a release is tagged that a consumer actually reads. Component releases (currently protocol `0.1.1`) advance freely and independently of the wire freeze, since consumers only ever reach Protocol through an immutable tagged ref. **Practical implication for Architecture**: any schema change to close a `target_gaps` item above (e.g. adding a `$ref` to `ledger-entry.deferred`, which is a newly-required-property-equivalent tightening) can be made directly on `1.0` today without migration machinery — it only needs a new fixture pair and a new Protocol release/tag for consumers to pick up.\n\n## fixture_inventory (63 total: 27 valid, 36 invalid)\nGrouped by schema; each accepting fixture is a worked positive example, each rejecting fixture proves one named constraint via `expect.pointer`/`expect.message`.\n\n**`invocation`** — valid: `invocation.root.json` (depth 0, self-rooted budget), `invocation.coding.json` (depth 1, continued lineage, non-empty `trusted_inputs`/`guides`). invalid: `unstable-id` (id pattern), `malformed-forgejo-uri` (uri format), `repository-output-injection` (repository pattern, blocks newline/argument-injection payloads), `untrusted-inline-input` (`additionalProperties:false` rejects an `untrusted_inputs` key — proves the schema has no concept of untrusted content, it simply rejects the unknown key), `unbudgeted` (`budget` required), `rootless-lineage` (root invocation must root its own budget), `depth-past-ceiling` (depth vs `max_continuation_depth`).\n\n**`capability-result`** — valid: `complete`, `committed-paths` (git.commit_push effect + matching mutation), `needs-input`, `causal-wait`, `failed`, `ordered-continuations` (continuation `after` ordering + both provider resolution rules). invalid: `needs-input-without-question`, `causal-wait-without-wait`, `failure-without-diagnosis`, `workflow-text-continuation` (rejects a `prompt` key — continuations name capabilities, not executable text), `dangling-after`, `continuation-cycle`, `undeclared-commit-paths` (git.commit_push requires `parameters.paths`), `unreported-mutation` (mutation must name a declared effect).\n\n**`completion-evidence`** — valid: `merge` (review-only), `verified-operation` (environment+operations, two findings). invalid: `no-findings` (`minItems:1`), `unevidenced-finding` (finding `evidence` non-empty), `abbreviated-head` (`head_sha minLength:7`), `merge-carrying-environment` (merge boundary forbids `environment`/`operations`), `environmentless-operation` (verified_operation requires `environment`), `unwitnessed-operation` (verified_operation requires `operations`).\n\n**`finding`** — valid: `command-pass`, `command-unavailable`. invalid: `unknown-verdict`, `missing-policy-effect`.\n\n**`reading`** — valid: `command`, `unavailable` (`acquisition.reliable:false`, `observation:null`). invalid: `anonymous-producer` (`producer.tool` required), `unbound-observation-time` (`observed_at` date-time format), `verdict-in-observation` (`additionalProperties:false` rejects a top-level `verdict` key — proves \"a sensor reading never contains a policy verdict\" per `CLAUDE.md`).\n\n**`ledger-entry`** — valid: `reserved`, `deferred-dependency`, `deferred-join`. invalid: `dependency-without-prerequisites`, `foreign-lineage` (invocation_id must descend from root), `join-without-invocation` (`deferred` required for either deferred kind).\n\n**`event`** — valid: `invocation-completed`, `invocation-failed`, `graph-reserved`, `graph-deferred-dependency`, `graph-deferred-join`, `graph-released` (claimed:true), `graph-released-unclaimed` (claimed:false), `graph-completed`. invalid: `foreign-type-namespace` (`vendor.*` type rejected), `unregistered-graph-transition` (`homeostat.graph.claimed` not in the type registry).\n\n**`compatibility`** — valid: `component`. invalid: `no-readable-version` (`reads minItems:1`), `unstable-release` (`release` pattern rejects `\"latest\"`), `malformed-wire-version` (`reads[]` pattern rejects `\"banana\"`).\n\n**`policy`** — valid: `minimal`. invalid: `self-review-permitted` (`require_opposite_provider_review` must be `true`), `unbudgeted` (`completion.budget` required), `unknown-provider` (provider enum rejects `\"gemini\"`). #### Evidence - {'kind': 'repository_state', 'repository': 'homeostat/protocol', 'working_tree_head': '9187dfac86bebfbdf8ec256cb1ab7cd63e9d4289', 'note': 'Working tree HEAD differs from the trusted-input pinned revision 3971f7428ee391eb4a5056090e08d7018bdde501 (tag v0.1.1); no git CLI tool was available to diff them directly. VERSION at both the top-level tree and the pinned checkout materialized at .homeostat/protocol/ reads 0.1.1, and a byte-for-byte comparison of schemas/capability-result.schema.json, NOTES.md, and the full fixtures/ file list between the top level and .homeostat/protocol/ found no differences, so the schema/fixture graph inventoried below is the v0.1.1 graph.'} - {'kind': 'file', 'path': 'schemas/*.schema.json (8 files)'} - {'kind': 'file', 'path': 'fixtures/valid/*.json (27 files) and fixtures/invalid/*.json (36 files)'} - {'kind': 'file', 'path': 'tools/validate.py'} - {'kind': 'file', 'path': 'NOTES.md#$ref-composition-between-schemas'} - {'kind': 'file', 'path': 'docs/versioning.md'} - {'kind': 'file', 'path': 'AGENTS.md'} - {'kind': 'file', 'path': 'README.md'} - {'kind': 'file', 'path': 'pyproject.toml'} - {'kind': 'file', 'path': '.forgejo/workflows/validate.yaml'} - {'kind': 'file', 'path': '.homeostat/consumer/.forgejo/workflows/homeostat-intake.yaml'} - {'kind': 'file', 'path': '.homeostat/consumer/tools/validate.py'} #### Findings None recorded. #### Requested effects No deterministic effect requested. #### Mutations None recorded. #### Continuation No continuation requested. Completion: `none / not_reached`
Author
Owner

Homeostat invocation issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:settle-composite-schema-architecture

Status: needs_input
Capability: architecture
Provider/model: codex/default

Input to AI

The architecture can be finalized after the pinned schema graph and validation behavior are known.

Subject: Finalize composite-schema reference, bundling, mapping, and fixture architecture

Output from AI

The composite-schema architecture cannot be finalized from the supplied evidence. The trusted capability result identifies the prerequisite architecture invocation but does not include its findings, field mapping, or pinned schema-graph evidence. Direct inspection of revision 3971f7428e was also unavailable because the local read-only execution sandbox could not initialize. Finalizing paths, identifiers, references, generated artifacts, validator checks, fixture topology, compatibility classification, rollout order, or migration records without that evidence would require inventing repository decisions.

Evidence

  • {'kind': 'trusted_input', 'invocation_id': 'issue-13:research:plan-composite-schema-assertions:architecture-composite-schema', 'observation': 'The capability-result reference was supplied without its response, findings, or evidence.'}
  • {'kind': 'continuation_input', 'repository': 'homeostat/protocol', 'revision': '3971f7428e', 'observation': 'The required evidence shape demands a total field mapping and exact repository-specific paths, identifiers, commands, checks, fixtures, compatibility classification, rollout order, and migration record.'}
  • {'kind': 'environment_probe', 'operation': 'Read repository status, HEAD, and file inventory', 'result': 'blocked', 'diagnostic': 'The execution sandbox failed to create its required namespace, so no repository files were inspected.'}

Findings

  • {'id': 'missing-pinned-schema-graph', 'classification': 'blocking_unknown', 'detail': 'Canonical schema identifiers, the current composite fields, available part-schema targets, reference resolution behavior, bundling implementation, and validator behavior are not present in the invocation payload.'}
  • {'id': 'architecture-must-remain-repository-specific', 'classification': 'boundary', 'detail': 'Protocol repository guidance requires explicit compatibility treatment, a migration record for every transition, accepting fixtures for new schemas, rejecting fixtures for new constraints, and validation with python3 tools/validate.py.'}
  • {'id': 'no-safe-decision-record', 'classification': 'effect', 'detail': 'Recording a Forgejo architecture decision now would make unsupported repository-specific claims, so no effect is requested.'}

Requested effects

No deterministic effect requested.

Mutations

None recorded.

Continuation

  • Architecture depends on exact facts absent from the supplied trusted result and unavailable through the current local sandbox.

Completion: none / not_reached

<!-- homeostat:invocation:issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:settle-composite-schema-architecture --> ### Homeostat invocation `issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:settle-composite-schema-architecture` Status: **needs_input** Capability: `architecture` Provider/model: `codex/default` #### Input to AI The architecture can be finalized after the pinned schema graph and validation behavior are known. Subject: Finalize composite-schema reference, bundling, mapping, and fixture architecture #### Output from AI The composite-schema architecture cannot be finalized from the supplied evidence. The trusted capability result identifies the prerequisite architecture invocation but does not include its findings, field mapping, or pinned schema-graph evidence. Direct inspection of revision 3971f7428ee391eb4a5056090e08d7018bdde501 was also unavailable because the local read-only execution sandbox could not initialize. Finalizing paths, identifiers, references, generated artifacts, validator checks, fixture topology, compatibility classification, rollout order, or migration records without that evidence would require inventing repository decisions. #### Evidence - {'kind': 'trusted_input', 'invocation_id': 'issue-13:research:plan-composite-schema-assertions:architecture-composite-schema', 'observation': 'The capability-result reference was supplied without its response, findings, or evidence.'} - {'kind': 'continuation_input', 'repository': 'homeostat/protocol', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501', 'observation': 'The required evidence shape demands a total field mapping and exact repository-specific paths, identifiers, commands, checks, fixtures, compatibility classification, rollout order, and migration record.'} - {'kind': 'environment_probe', 'operation': 'Read repository status, HEAD, and file inventory', 'result': 'blocked', 'diagnostic': 'The execution sandbox failed to create its required namespace, so no repository files were inspected.'} #### Findings - {'id': 'missing-pinned-schema-graph', 'classification': 'blocking_unknown', 'detail': 'Canonical schema identifiers, the current composite fields, available part-schema targets, reference resolution behavior, bundling implementation, and validator behavior are not present in the invocation payload.'} - {'id': 'architecture-must-remain-repository-specific', 'classification': 'boundary', 'detail': 'Protocol repository guidance requires explicit compatibility treatment, a migration record for every transition, accepting fixtures for new schemas, rejecting fixtures for new constraints, and validation with python3 tools/validate.py.'} - {'id': 'no-safe-decision-record', 'classification': 'effect', 'detail': 'Recording a Forgejo architecture decision now would make unsupported repository-specific claims, so no effect is requested.'} #### Requested effects No deterministic effect requested. #### Mutations None recorded. #### Continuation - Architecture depends on exact facts absent from the supplied trusted result and unavailable through the current local sandbox. Completion: `none / not_reached`
Author
Owner

Homeostat invocation issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:settle-composite-schema-architecture:research-pinned-composite-schema-graph

Status: complete
Capability: research
Provider/model: claude/default

Input to AI

Architecture depends on exact facts absent from the supplied trusted result and unavailable through the current local sandbox.

Subject: Recover pinned composite-schema graph and validation behavior

Output from AI

All facts below are sourced from the pinned checkout at .homeostat/protocol/, whose .git/HEAD resolves to exactly the requested revision 3971f7428ee391eb4a5056090e08d7018bdde501 (tag v0.1.1; .git/logs/HEAD: "checkout: moving from master to refs/tags/v0.1.1"). Note this is not the main worktree — /workspace/homeostat/protocol is on branch main at 9187dfac86bebfbdf8ec256cb1ab7cd63e9d4289, a different commit. I spot-checked the two on tools/validate.py and NOTES.md and they are byte-identical, but everything cited below uses the pinned checkout's paths and is anchored to 3971f7428ee391eb4a5056090e08d7018bdde501 per the exact-revision requirement.

Schemas and $id (all wire 1.0)

9 schemas in schemas/, each "schema_version": {"const": "1.0"} and $id of the form homeostat://protocol/<stem>/1.0: capability-result, invocation, completion-evidence, finding, reading, policy, event, ledger-entry, compatibility. Zero $ref occurrences anywhere in schemas/ — the only textual match for the string $ref in the whole checkout is prose in NOTES.md:113, not an actual schema construct.

Total composite-field mapping

Every field below is currently {"type": "object"} (or an array of it) — structurally unconstrained.

Field (schema:pointer) NOTES.md deferred item? Fixture-confirmed shape Existing part-schema fit
capability-result.schema.json#/properties/findings (capability-result.schema.json:16) yes Every fixture uses [] (e.g. fixtures/valid/capability-result.complete.json:9) — no non-empty example exists Plausible but unconfirmed: finding.schema.json (id, verdict, policy_effect, evidence overlap), no fixture proves it
capability-result.schema.json#/properties/evidence (:15) yes Confirmed shape {"source": "...", "revision": "..."} (fixtures/valid/capability-result.complete.json:8, repeated in every capability-result fixture) No existing schema fits. Does not match reading.schema.json (no sensor/observed_at/producer) or finding.schema.json. Would need a new minimal citation schema or stay generic
capability-result.schema.json#/properties/completion/properties/evidence (:126) yes (NOTES.md's "completion.evidence") Every fixture uses [] Plausible but unconfirmed: completion-evidence.schema.json by name/purpose, no fixture proves it
invocation.schema.json#/properties/trusted_inputs (invocation.schema.json:39) yes Polymorphic, discriminated by kind: {"kind":"issue_body","revision":...,"text":...} (fixtures/valid/invocation.root.json:20-24); this very invocation's own trusted_inputs additionally carries {"kind":"capability_result","capability":...,"invocation_id":...} (a pointer, not an embedded document) and {"kind":"continuation_inputs","value":{...}} (free-form) No single $ref fits. Needs a oneOf discriminated union keyed on kind; the capability_result variant would $ref capability-result.schema.json itself (self-referential composite)
invocation.schema.json#/properties/readings (:41) yes Every fixture uses [] Cleanest 1:1 candidate by name/purpose: reading.schema.json — but still no fixture with content confirms it
invocation.schema.json#/properties/guides (:40) yes Confirmed shapes: {"kind":"persona","path":"..."} (fixtures/valid/invocation.coding.json:27-30) and {"path":"...","revision":"..."} (fixtures/valid/ledger-entry.deferred-dependency.json:25) No existing schema fits. Neither finding nor reading models a guide; would need a new guide.schema.json
ledger-entry.schema.json#/properties/deferred (ledger-entry.schema.json:39-42) no — omitted from NOTES.md's list Fully confirmed: fixtures/valid/ledger-entry.deferred-dependency.json:10-36 and .../ledger-entry.deferred-join.json embed a complete, schema-conformant invocation envelope (every required property present: schema_version, id, parent_id, capability, assignment, artifact, provider, trusted_inputs, guides, readings, allowed_effects, versions, budget) Confirmed 1:1 match: invocation.schema.json. This field should be in scope alongside NOTES.md's six, and it's the only one with proof, not inference

Important adjacent fact: completion-evidence.schema.json#/properties/findings (completion-evidence.schema.json:13-29) is not bare — it already has a full inline item schema, and that shape is deliberately incompatible with finding.schema.json: it lacks evaluator, expectation, schema_version, and uses subject_revision (string) instead of subject (object). Any $ref remediation must not collapse capability-result.findings and completion-evidence.findings onto the same target — they are different shapes sharing a field name.

Resolver behavior (why $ref would not work today) — tools/validate.py

  • ROOT = Path(__file__).resolve().parents[1]; SCHEMAS/VALID/INVALID are siblings under it (tools/validate.py:22-25).
  • Each schema file is loaded and validated independently: Draft202012Validator(schema, format_checker=checker) (:71), with schemas collected into a flat dict[stem, schema] (:266-274) used only to look up which schema a fixture belongs to — never wired into a Registry/resolver for cross-file $ref.
  • Confirms NOTES.md:105-121: governor invokes transducer with single schema file paths (--invocation-schema, --result-schema), not a directory, so a $ref to homeostat://protocol/finding/1.0 would not resolve at that boundary either, independent of what Protocol does internally.
  • Fact, not inference: adding a $ref today, unchanged, breaks validation both in this repo's tools/validate.py (unresolvable-reference error) and at the governor→transducer boundary.
  • pyproject.toml:6 pins jsonschema[format]>=4.10, uncapped. A registry-based fix (referencing.Registry passed as Draft202012Validator(schema, registry=..., format_checker=...)) needs jsonschema's referencing-based API, stable from roughly 4.18+ — the dependency floor may need raising as part of this change.
  • No bundling/generation command exists (explicit unavailable value — verified by grep for bundle|generate|resolver|registry across the checkout; the only hit is an unrelated docstring use of "registry" in event.schema.json's type enum description). NOTES.md's own proposed direction (:119-121) is "publish a bundled schema that resolves internally, leaving the single-file invocation intact" — this is a design to build, not something present today.
  • tools/validate.py also carries four Python-level semantic-rule functions keyed by schema stem (SEMANTIC_RULES at :247-252: capability-result, completion-evidence, invocation, ledger-entry) that check referential rules JSON Schema can't express (dangling continuation after, orphaned mutations, lineage vs. budget, etc.). Any cross-field rule the new composite constraints need (e.g. "a trusted_inputs item of kind capability_result names a real prior invocation") belongs here, not in the schema.

Fixture organization rule

  • Stem match: fixture_stem = path.name.split(".", 1)[0] (:40) must equal a schemas/<stem>.schema.json.
  • fixtures/valid/<stem>.<variant>.json — a raw accepted document.
  • fixtures/invalid/<stem>.<variant>.json{"document": ..., "expect": {"pointer": ..., "message": ...}}; the rejection must land at the exact declared pointer with the declared message substring (:294-315).
  • Coverage is enforced: every schema needs ≥1 accepting fixture or the run fails (:289-292).
  • Consequence: turning any of the 7 fields above into a real $ref/oneOf requires, per field, at least one new accepting fixture showing valid nested content and one new rejecting fixture whose expect.pointer is the exact JSON-pointer jsonschema reports for a violation inside the referenced part (e.g. a findings item missing evaluator would report at /findings/0/evaluator).

docs/versioning.md compatibility classification

  • Two independent axes: wire version (schema_version + $id MAJOR.MINOR) vs. component release (VERSION, compatibility.json.release) — docs/versioning.md:6-18.
  • Minor (1.01.1): additive/loosening only (:37-42).
  • Major (1.x2.0): adding required properties, tightening, or additionalProperties: false (:44-51) — explicitly including tightening even though it's a strict subset of the old shape, "because a conforming producer written against the old version now emits documents the new version rejects" (:53-55).
  • Classification: replacing any of the 7 bare fields above with a $ref/oneOf onto a schema that has additionalProperties: false and required properties (true of finding, reading, invocation, completion-evidence alike) is a major/tightening change by this rule.
  • But it doesn't matter right now: docs/versioning.md:73-78 and NOTES.md:8-27 record the owner's pre-release decision (2026-07-26, amended 2026-07-28) that wire 1.0 is unpublished — schemas "may be tightened or reshaped in place without a version advance or migration entry" until the first tagged release. So today this lands directly on 1.0, no bump, no migration. The major-change rule only starts binding "from the first tagged release onward."
  • Rollout order, for when the wire is live: docs/versioning.md:70-71 — readers move first ("Steps 5 and 6 do not commute. Readers accept the new shape before any writer emits it").

Migration records

  • migrations/ contains only README.md — no <from>-to-<to> subdirectories exist (glob-confirmed at this revision). Zero migrations have ever been recorded, consistent with the unpublished-wire decision.
  • migrations/README.md:13-15: "A migration that cannot be mechanical says so... Stating 'documents at 1.0 are not rewritten...' is a complete migration when it is true."
  • Proposed migration-record path: none is required today, because this change stays on 1.0 under the pre-release exception — there is no <from>-to-<to> pair to name. The correct record of this work under current rules is the existing NOTES.md "Deferred, with a reason" → "$ref composition between schemas" entry (NOTES.md:97-121), which should be updated/closed in place once the fix lands, rather than a new migrations/ directory being created. If the team wants a migration entry pre-emptively anyway, migrations/1.0-to-1.0-composite-refs/README.md would break the directory-naming convention (<wire>-to-<wire>, both always distinct); that convention has no precedent for a same-version entry, so this should be raised as an open question rather than assumed.

Adjacent fact worth flagging to architecture

The consumer's live checkout (.homeostat/consumer/compatibility.json, .forgejo/homeostat/versions.json) records transducer 0.1.5 / governor 0.1.2, while this invocation's own dispatch-time versions metadata says governor 0.1.1 / transducer 0.1.4 — consistent with the repo's most recent merge commit title, "Merge branch 'fix/governor-v0-1-2-refs'". The consumer has moved since this invocation was dispatched; treat the invocation metadata as a dispatch-time snapshot, not current consumer state, if current governor/transducer behavior matters to the composite-schema decision.

Evidence

  • {'source': 'git', 'path': '.homeostat/protocol/.git/HEAD', 'revision': '3971f7428e'}
  • {'source': 'file', 'path': 'schemas/capability-result.schema.json', 'revision': '3971f7428e'}
  • {'source': 'file', 'path': 'schemas/invocation.schema.json', 'revision': '3971f7428e'}
  • {'source': 'file', 'path': 'schemas/completion-evidence.schema.json', 'revision': '3971f7428e'}
  • {'source': 'file', 'path': 'schemas/ledger-entry.schema.json', 'revision': '3971f7428e'}
  • {'source': 'file', 'path': 'tools/validate.py', 'revision': '3971f7428e'}
  • {'source': 'file', 'path': 'docs/versioning.md', 'revision': '3971f7428e'}
  • {'source': 'file', 'path': 'NOTES.md', 'revision': '3971f7428e'}
  • {'source': 'file', 'path': 'migrations/README.md', 'revision': '3971f7428e'}

Findings

  • Requested revision 3971f7428e (tag v0.1.1) is materialized verbatim at .homeostat/protocol/ (.git/HEAD == that hash; .git/logs/HEAD shows checkout to refs/tags/v0.1.1). All findings below are sourced from that checkout, not the main worktree, whose HEAD is the unrelated 9187dfac86 on branch main.
  • Zero '$ref' occurrences in any schemas/*.schema.json at this revision; the only textual match for '$ref' in the whole checkout is prose in NOTES.md:113.
  • tools/validate.py builds no jsonschema Registry/RefResolver; it instantiates Draft202012Validator(schema, format_checker=checker) per schema file independently (validate.py:71, 266-274). No bundling, generation, or deterministic-output-check tooling exists anywhere in the repository (grep for bundle/generate/resolver/registry found only an unrelated 'registry' in event.schema.json's enum docstring).
  • 7 composite fields across 3 schemas are currently bare '{"type": "object"}' with no $ref: capability-result.findings, capability-result.evidence, capability-result.completion.evidence, invocation.trusted_inputs, invocation.readings, invocation.guides, and ledger-entry.deferred. NOTES.md's own deferred-work list (NOTES.md:97-103) names the first 6 but omits ledger-entry.deferred, which is structurally identical (bare object) and is the one field with a fully confirmed fixture match.

Requested effects

No deterministic effect requested.

Mutations

None recorded.

Continuation

No continuation requested.

Completion: none / reached

<!-- homeostat:invocation:issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:settle-composite-schema-architecture:research-pinned-composite-schema-graph --> ### Homeostat invocation `issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:settle-composite-schema-architecture:research-pinned-composite-schema-graph` Status: **complete** Capability: `research` Provider/model: `claude/default` #### Input to AI Architecture depends on exact facts absent from the supplied trusted result and unavailable through the current local sandbox. Subject: Recover pinned composite-schema graph and validation behavior #### Output from AI All facts below are sourced from the pinned checkout at `.homeostat/protocol/`, whose `.git/HEAD` resolves to exactly the requested revision `3971f7428ee391eb4a5056090e08d7018bdde501` (tag `v0.1.1`; `.git/logs/HEAD`: "checkout: moving from master to refs/tags/v0.1.1"). Note this is *not* the main worktree — `/workspace/homeostat/protocol` is on branch `main` at `9187dfac86bebfbdf8ec256cb1ab7cd63e9d4289`, a different commit. I spot-checked the two on `tools/validate.py` and `NOTES.md` and they are byte-identical, but everything cited below uses the pinned checkout's paths and is anchored to `3971f7428ee391eb4a5056090e08d7018bdde501` per the exact-revision requirement. ## Schemas and $id (all wire 1.0) 9 schemas in `schemas/`, each `"schema_version": {"const": "1.0"}` and `$id` of the form `homeostat://protocol/<stem>/1.0`: `capability-result`, `invocation`, `completion-evidence`, `finding`, `reading`, `policy`, `event`, `ledger-entry`, `compatibility`. **Zero `$ref` occurrences anywhere in `schemas/`** — the only textual match for the string `$ref` in the whole checkout is prose in `NOTES.md:113`, not an actual schema construct. ## Total composite-field mapping Every field below is currently `{"type": "object"}` (or an array of it) — structurally unconstrained. | Field (schema:pointer) | NOTES.md deferred item? | Fixture-confirmed shape | Existing part-schema fit | |---|---|---|---| | `capability-result.schema.json#/properties/findings` (`capability-result.schema.json:16`) | yes | Every fixture uses `[]` (e.g. `fixtures/valid/capability-result.complete.json:9`) — no non-empty example exists | Plausible but **unconfirmed**: `finding.schema.json` (`id`, `verdict`, `policy_effect`, `evidence` overlap), no fixture proves it | | `capability-result.schema.json#/properties/evidence` (`:15`) | yes | Confirmed shape `{"source": "...", "revision": "..."}` (`fixtures/valid/capability-result.complete.json:8`, repeated in every capability-result fixture) | **No existing schema fits.** Does not match `reading.schema.json` (no `sensor`/`observed_at`/`producer`) or `finding.schema.json`. Would need a new minimal citation schema or stay generic | | `capability-result.schema.json#/properties/completion/properties/evidence` (`:126`) | yes (NOTES.md's "completion.evidence") | Every fixture uses `[]` | Plausible but **unconfirmed**: `completion-evidence.schema.json` by name/purpose, no fixture proves it | | `invocation.schema.json#/properties/trusted_inputs` (`invocation.schema.json:39`) | yes | **Polymorphic**, discriminated by `kind`: `{"kind":"issue_body","revision":...,"text":...}` (`fixtures/valid/invocation.root.json:20-24`); this very invocation's own `trusted_inputs` additionally carries `{"kind":"capability_result","capability":...,"invocation_id":...}` (a pointer, not an embedded document) and `{"kind":"continuation_inputs","value":{...}}` (free-form) | **No single $ref fits.** Needs a `oneOf` discriminated union keyed on `kind`; the `capability_result` variant would `$ref` `capability-result.schema.json` itself (self-referential composite) | | `invocation.schema.json#/properties/readings` (`:41`) | yes | Every fixture uses `[]` | Cleanest 1:1 candidate by name/purpose: `reading.schema.json` — but still **no fixture with content confirms it** | | `invocation.schema.json#/properties/guides` (`:40`) | yes | Confirmed shapes: `{"kind":"persona","path":"..."}` (`fixtures/valid/invocation.coding.json:27-30`) and `{"path":"...","revision":"..."}` (`fixtures/valid/ledger-entry.deferred-dependency.json:25`) | **No existing schema fits.** Neither `finding` nor `reading` models a guide; would need a new `guide.schema.json` | | `ledger-entry.schema.json#/properties/deferred` (`ledger-entry.schema.json:39-42`) | **no — omitted from NOTES.md's list** | **Fully confirmed**: `fixtures/valid/ledger-entry.deferred-dependency.json:10-36` and `.../ledger-entry.deferred-join.json` embed a complete, schema-conformant `invocation` envelope (every required property present: `schema_version, id, parent_id, capability, assignment, artifact, provider, trusted_inputs, guides, readings, allowed_effects, versions, budget`) | **Confirmed 1:1 match**: `invocation.schema.json`. This field should be in scope alongside NOTES.md's six, and it's the only one with proof, not inference | Important adjacent fact: `completion-evidence.schema.json#/properties/findings` (`completion-evidence.schema.json:13-29`) is **not** bare — it already has a full inline item schema, and that shape is deliberately *incompatible* with `finding.schema.json`: it lacks `evaluator`, `expectation`, `schema_version`, and uses `subject_revision` (string) instead of `subject` (object). Any $ref remediation must not collapse `capability-result.findings` and `completion-evidence.findings` onto the same target — they are different shapes sharing a field name. ## Resolver behavior (why $ref would not work today) — `tools/validate.py` - `ROOT = Path(__file__).resolve().parents[1]`; `SCHEMAS/VALID/INVALID` are siblings under it (`tools/validate.py:22-25`). - Each schema file is loaded and validated **independently**: `Draft202012Validator(schema, format_checker=checker)` (`:71`), with schemas collected into a flat `dict[stem, schema]` (`:266-274`) used only to look up which schema a fixture belongs to — never wired into a `Registry`/resolver for cross-file `$ref`. - Confirms `NOTES.md:105-121`: `governor` invokes `transducer` with single schema **file** paths (`--invocation-schema`, `--result-schema`), not a directory, so a `$ref` to `homeostat://protocol/finding/1.0` would not resolve at that boundary either, independent of what Protocol does internally. - **Fact, not inference**: adding a `$ref` today, unchanged, breaks validation both in this repo's `tools/validate.py` (unresolvable-reference error) and at the governor→transducer boundary. - `pyproject.toml:6` pins `jsonschema[format]>=4.10`, uncapped. A registry-based fix (`referencing.Registry` passed as `Draft202012Validator(schema, registry=..., format_checker=...)`) needs jsonschema's `referencing`-based API, stable from roughly 4.18+ — the dependency floor may need raising as part of this change. - **No bundling/generation command exists** (explicit unavailable value — verified by grep for `bundle|generate|resolver|registry` across the checkout; the only hit is an unrelated docstring use of "registry" in `event.schema.json`'s `type` enum description). NOTES.md's own proposed direction (`:119-121`) is "publish a bundled schema that resolves internally, leaving the single-file invocation intact" — this is a design to build, not something present today. - `tools/validate.py` also carries four Python-level semantic-rule functions keyed by schema stem (`SEMANTIC_RULES` at `:247-252`: `capability-result`, `completion-evidence`, `invocation`, `ledger-entry`) that check referential rules JSON Schema can't express (dangling continuation `after`, orphaned mutations, lineage vs. budget, etc.). Any cross-field rule the new composite constraints need (e.g. "a `trusted_inputs` item of kind `capability_result` names a real prior invocation") belongs here, not in the schema. ## Fixture organization rule - Stem match: `fixture_stem = path.name.split(".", 1)[0]` (`:40`) must equal a `schemas/<stem>.schema.json`. - `fixtures/valid/<stem>.<variant>.json` — a raw accepted document. - `fixtures/invalid/<stem>.<variant>.json` — `{"document": ..., "expect": {"pointer": ..., "message": ...}}`; the rejection must land at the exact declared pointer with the declared message substring (`:294-315`). - Coverage is enforced: every schema needs ≥1 accepting fixture or the run fails (`:289-292`). - Consequence: turning any of the 7 fields above into a real `$ref`/`oneOf` requires, per field, at least one new accepting fixture showing valid nested content and one new rejecting fixture whose `expect.pointer` is the exact JSON-pointer jsonschema reports for a violation inside the referenced part (e.g. a `findings` item missing `evaluator` would report at `/findings/0/evaluator`). ## `docs/versioning.md` compatibility classification - Two independent axes: wire version (`schema_version` + `$id` MAJOR.MINOR) vs. component release (`VERSION`, `compatibility.json.release`) — `docs/versioning.md:6-18`. - Minor (`1.0`→`1.1`): additive/loosening only (`:37-42`). - Major (`1.x`→`2.0`): adding required properties, tightening, or `additionalProperties: false` (`:44-51`) — explicitly including tightening even though it's a strict subset of the old shape, "because a conforming producer written against the old version now emits documents the new version rejects" (`:53-55`). - **Classification**: replacing any of the 7 bare fields above with a `$ref`/`oneOf` onto a schema that has `additionalProperties: false` and required properties (true of `finding`, `reading`, `invocation`, `completion-evidence` alike) is a **major/tightening change** by this rule. - **But it doesn't matter right now**: `docs/versioning.md:73-78` and `NOTES.md:8-27` record the owner's pre-release decision (2026-07-26, amended 2026-07-28) that wire `1.0` is **unpublished** — schemas "may be tightened or reshaped in place without a version advance or migration entry" until the first tagged release. So today this lands directly on `1.0`, no bump, no migration. The major-change rule only starts binding "from the first tagged release onward." - Rollout order, for when the wire *is* live: `docs/versioning.md:70-71` — readers move first ("Steps 5 and 6 do not commute. Readers accept the new shape before any writer emits it"). ## Migration records - `migrations/` contains **only** `README.md` — no `<from>-to-<to>` subdirectories exist (glob-confirmed at this revision). Zero migrations have ever been recorded, consistent with the unpublished-wire decision. - `migrations/README.md:13-15`: "A migration that cannot be mechanical says so... Stating 'documents at 1.0 are not rewritten...' is a complete migration when it is true." - **Proposed migration-record path**: none is required today, because this change stays on `1.0` under the pre-release exception — there is no `<from>-to-<to>` pair to name. The correct record of this work under current rules is the existing `NOTES.md` "Deferred, with a reason" → "`$ref` composition between schemas" entry (`NOTES.md:97-121`), which should be updated/closed in place once the fix lands, rather than a new `migrations/` directory being created. If the team wants a migration entry pre-emptively anyway, `migrations/1.0-to-1.0-composite-refs/README.md` would break the directory-naming convention (`<wire>-to-<wire>`, both always distinct); that convention has no precedent for a same-version entry, so this should be raised as an open question rather than assumed. ## Adjacent fact worth flagging to architecture The consumer's live checkout (`.homeostat/consumer/compatibility.json`, `.forgejo/homeostat/versions.json`) records `transducer 0.1.5` / `governor 0.1.2`, while this invocation's own dispatch-time `versions` metadata says `governor 0.1.1` / `transducer 0.1.4` — consistent with the repo's most recent merge commit title, "Merge branch 'fix/governor-v0-1-2-refs'". The consumer has moved since this invocation was dispatched; treat the invocation metadata as a dispatch-time snapshot, not current consumer state, if current governor/transducer behavior matters to the composite-schema decision. #### Evidence - {'source': 'git', 'path': '.homeostat/protocol/.git/HEAD', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501'} - {'source': 'file', 'path': 'schemas/capability-result.schema.json', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501'} - {'source': 'file', 'path': 'schemas/invocation.schema.json', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501'} - {'source': 'file', 'path': 'schemas/completion-evidence.schema.json', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501'} - {'source': 'file', 'path': 'schemas/ledger-entry.schema.json', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501'} - {'source': 'file', 'path': 'tools/validate.py', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501'} - {'source': 'file', 'path': 'docs/versioning.md', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501'} - {'source': 'file', 'path': 'NOTES.md', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501'} - {'source': 'file', 'path': 'migrations/README.md', 'revision': '3971f7428ee391eb4a5056090e08d7018bdde501'} #### Findings - Requested revision 3971f7428ee391eb4a5056090e08d7018bdde501 (tag v0.1.1) is materialized verbatim at .homeostat/protocol/ (.git/HEAD == that hash; .git/logs/HEAD shows checkout to refs/tags/v0.1.1). All findings below are sourced from that checkout, not the main worktree, whose HEAD is the unrelated 9187dfac86bebfbdf8ec256cb1ab7cd63e9d4289 on branch main. - Zero '$ref' occurrences in any schemas/*.schema.json at this revision; the only textual match for '$ref' in the whole checkout is prose in NOTES.md:113. - tools/validate.py builds no jsonschema Registry/RefResolver; it instantiates Draft202012Validator(schema, format_checker=checker) per schema file independently (validate.py:71, 266-274). No bundling, generation, or deterministic-output-check tooling exists anywhere in the repository (grep for bundle/generate/resolver/registry found only an unrelated 'registry' in event.schema.json's enum docstring). - 7 composite fields across 3 schemas are currently bare '{"type": "object"}' with no $ref: capability-result.findings, capability-result.evidence, capability-result.completion.evidence, invocation.trusted_inputs, invocation.readings, invocation.guides, and ledger-entry.deferred. NOTES.md's own deferred-work list (NOTES.md:97-103) names the first 6 but omits ledger-entry.deferred, which is structurally identical (bare object) and is the one field with a fully confirmed fixture match. #### Requested effects No deterministic effect requested. #### Mutations None recorded. #### Continuation No continuation requested. Completion: `none / reached`
Author
Owner

Delivered by consolidated Protocol PR #5

The architecture decision is embodied by self-contained local $defs/$ref composition in the canonical schemas.

The four planned concerns converged into one atomic schema/bundling change: homeostat/protocol#5, merged as 3dd64b86a446fb72dcc5109c6c0e5c5ab1453c5d. Protocol v0.1.2 (572d418) carries the result and is the active Homeostat pin. Closing this issue as delivered.

## Delivered by consolidated Protocol PR #5 The architecture decision is embodied by self-contained local `$defs`/`$ref` composition in the canonical schemas. The four planned concerns converged into one atomic schema/bundling change: homeostat/protocol#5, merged as `3dd64b86a446fb72dcc5109c6c0e5c5ab1453c5d`. Protocol v0.1.2 (`572d418`) carries the result and is the active Homeostat pin. Closing this issue as delivered.
korr closed this issue 2026-07-29 05:29:50 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
homeostat/protocol#1
No description provided.