Architecture: settle composite-schema $ref composition #1
Labels
No labels
homeostat/state
active
homeostat/state
attention
homeostat/state
ready
homeostat/state
waiting
homeostat/type
control
homeostat/type
intake
homeostat/type
work
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
#3 Add the part schemas required by composite protocol fields
homeostat/protocol
#2 Implement deterministic bundling for composite protocol schemas
homeostat/protocol
Reference
homeostat/protocol#1
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 invocation
issue-13:research:plan-composite-schema-assertions:architecture-composite-schemaStatus: causal_wait
Capability:
architectureProvider/model:
codex/defaultInput 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
3971f7428e', 'tag': 'v0.1.1', 'notes_reference': 'protocol/NOTES.md#$ref-composition-between-schemas'}Findings
Requested effects
No deterministic effect requested.
Mutations
None recorded.
Continuation
Completion:
none / changes_requiredHomeostat invocation
issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:inventory-composite-schema-graphStatus: complete
Capability:
researchProvider/model:
claude/defaultInput 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(tagv0.1.1). The repository's working-tree HEAD is9187dfac86bebfbdf8ec256cb1ab7cd63e9d4289("Merge branch 'fix/governor-v0-1-2-refs'"), a later commit — no git tool was available to me to diff the two revisions directly.VERSIONis0.1.1at both the top level and at the pinned checkout materialized under.homeostat/protocol/, and every schema, fixture andNOTES.mdI 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 underschemas/, 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 wholeschemas/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 isobject/{}/an array of bare objects, intended to carry an instance (or reference) of another Protocol document, perNOTES.md#$ref-composition-between-schemas.grep \\$refoverschemas/returns zero matches — no schema in this repository resolves a$refto 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 (onlyboundary,staterequired insidecompletion) | 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,waitare 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 embeddedreadingdocument |\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 benull, seen inreading.unavailable.json) |{}(unconstrained) | none — deliberate perCLAUDE.md: "a sensor reading never contains a policy verdict," soobservationis 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 fromfinding.schema.json(omitsschema_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 asfinding.evidence|\n/properties/review| required iffboundary=merge| no | fully inlined | not composite |\n/properties/environment| required iffboundary=verified_operation| no | fully inlined | not composite |\n/properties/operations/items| required iffboundary=verified_operation,minItems:1| no | fully inlined, referencesreading_id/finding_idby 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 (kindin[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.mdstates this is deliberate: shapes vary perevent.typeand 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.*andcompletionare fully inlined.\n\n## target_gaps\n1.invocation.trusted_inputsitems — no schema of the shape actually used ({"kind","revision","text"}forissue_body, or{"capability","invocation_id","kind"}forcapability_result, per this very invocation's owntrusted_inputs). Neitherfindingnorreadingmatches; this needs a new part schema (e.g.trusted-input), not a$refto an existing one.\n2.invocation.guidesitems — shape used is{"kind":"persona","path":...}or{"path":...,"revision":...}. No existing schema matches; needs a newguidepart schema.\n3.capability-result.findings/.evidence, andfinding.subject/finding.evidence— even wherefinding.schema.jsonorreading.schema.jsonlook like candidates, real fixtures use id-reference shapes ({"reading_id": "..."}) rather than embedding a full reading/finding document. A$refto 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 embeddeddeferredinvocation omitsbudget.ledger(required byinvocation.schema.json) and would failinvocationvalidation today — the bug NOTES.md's$refsection doesn't call out, because it's outside thecapability-result/invocationlist it names.\n5.capability-result.completion.evidence— the best candidate for a straightforward$reftocompletion-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$refis 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 withDraft202012Validator.check_schemaand 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>.jsonnames the schema it must satisfy by<stem>;fixtures/invalid/<stem>.<variant>.jsonwraps adocumentplus anexpect: {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/danglingafter, mutation-to-effect linkage, budget/lineage consistency, ledger-root descent, finding↔operation cross-references). These are exactly the checks a real$refgraph 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 declaredexpect.pointer/messagesubstring.\n\n## packaging_behavior\n- Protocol has no build/bundle artifact.pyproject.tomldeclarespackages = []— it exists only sopip install .pulls thejsonschema[format]dependency fortools/validate.py; nothing is compiled or wheeled (contrast with Transducer, which ships a wheel consumed viahomeostat/governor/actions/setup-transducer@v0.1.2with a pinnedwheel-url/wheel-sha256).\n- Distribution is a verbatim pinned git checkout, confirmed in.forgejo/workflows/homeostat-intake.yaml(consumer repo):checkoutaction withrepository: homeostat/protocol,ref: ${{ vars.HOMEOSTAT_PROTOCOL_REF }},path: .homeostat/protocol. The same pattern recurs inhomeostat-work.yaml'sprotocol-refinput.HOMEOSTAT_PROTOCOL_REFis the immutable tag (e.g.v0.1.1), matchingcompatibility.json's declaredprotocol.releaseand the consumer'sversions.jsonprotocol_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-levelschemas/,fixtures/,NOTES.md.\n-NOTES.md#Distribution-is-not-declareddocuments that this pinned-checkout convention is implied bydocs/versioning.mdbut not stated outright in Protocol itself — a real, currently-undocumented gap Architecture may want to close.\n- NoCHANGELOG.md/release notes file exists;VERSION(0.1.1) plusNOTES.md's dated "Decided"/"Amended" entries are the only release history.\n\n## versioning_constraints (fromdocs/versioning.md)\n- Two independent axes: wire version (schema_version,MAJOR.MINORin each$id) vs component release (VERSION,compatibility.json.release). All current schemas are wire1.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, lowerminItems); 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, raiseminItems, addadditionalProperties: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 → writemigrations/<from>-to-<to>/README.md(+upgrade.pyif 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: wire1.0is 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 protocol0.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 atarget_gapsitem above (e.g. adding a$reftoledger-entry.deferred, which is a newly-required-property-equivalent tightening) can be made directly on1.0today 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 viaexpect.pointer/expect.message.\n\n**invocation** — valid:invocation.root.json(depth 0, self-rooted budget),invocation.coding.json(depth 1, continued lineage, non-emptytrusted_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:falserejects anuntrusted_inputskey — proves the schema has no concept of untrusted content, it simply rejects the unknown key),unbudgeted(budgetrequired),rootless-lineage(root invocation must root its own budget),depth-past-ceiling(depth vsmax_continuation_depth).\n\n**capability-result** — valid:complete,committed-paths(git.commit_push effect + matching mutation),needs-input,causal-wait,failed,ordered-continuations(continuationafterordering + both provider resolution rules). invalid:needs-input-without-question,causal-wait-without-wait,failure-without-diagnosis,workflow-text-continuation(rejects apromptkey — continuations name capabilities, not executable text),dangling-after,continuation-cycle,undeclared-commit-paths(git.commit_push requiresparameters.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(findingevidencenon-empty),abbreviated-head(head_sha minLength:7),merge-carrying-environment(merge boundary forbidsenvironment/operations),environmentless-operation(verified_operation requiresenvironment),unwitnessed-operation(verified_operation requiresoperations).\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.toolrequired),unbound-observation-time(observed_atdate-time format),verdict-in-observation(additionalProperties:falserejects a top-levelverdictkey — proves "a sensor reading never contains a policy verdict" perCLAUDE.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(deferredrequired 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.claimednot in the type registry).\n\n**compatibility** — valid:component. invalid:no-readable-version(reads minItems:1),unstable-release(releasepattern rejects\"latest\"),malformed-wire-version(reads[]pattern rejects\"banana\").\n\n**policy** — valid:minimal. invalid:self-review-permitted(require_opposite_provider_reviewmust betrue),unbudgeted(completion.budgetrequired),unknown-provider(provider enum rejects\"gemini\").Evidence
9187dfac86', 'note': 'Working tree HEAD differs from the trusted-input pinned revision3971f7428e(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.'}Findings
None recorded.
Requested effects
No deterministic effect requested.
Mutations
None recorded.
Continuation
No continuation requested.
Completion:
none / not_reachedHomeostat invocation
issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:settle-composite-schema-architectureStatus: needs_input
Capability:
architectureProvider/model:
codex/defaultInput 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
3971f7428ewas 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
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.'}Findings
Requested effects
No deterministic effect requested.
Mutations
None recorded.
Continuation
Completion:
none / not_reachedHomeostat invocation
issue-13:research:plan-composite-schema-assertions:architecture-composite-schema:settle-composite-schema-architecture:research-pinned-composite-schema-graphStatus: complete
Capability:
researchProvider/model:
claude/defaultInput 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/HEADresolves to exactly the requested revision3971f7428ee391eb4a5056090e08d7018bdde501(tagv0.1.1;.git/logs/HEAD: "checkout: moving from master to refs/tags/v0.1.1"). Note this is not the main worktree —/workspace/homeostat/protocolis on branchmainat9187dfac86bebfbdf8ec256cb1ab7cd63e9d4289, a different commit. I spot-checked the two ontools/validate.pyandNOTES.mdand they are byte-identical, but everything cited below uses the pinned checkout's paths and is anchored to3971f7428ee391eb4a5056090e08d7018bdde501per the exact-revision requirement.Schemas and $id (all wire 1.0)
9 schemas in
schemas/, each"schema_version": {"const": "1.0"}and$idof the formhomeostat://protocol/<stem>/1.0:capability-result,invocation,completion-evidence,finding,reading,policy,event,ledger-entry,compatibility. Zero$refoccurrences anywhere inschemas/— the only textual match for the string$refin the whole checkout is prose inNOTES.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.capability-result.schema.json#/properties/findings(capability-result.schema.json:16)[](e.g.fixtures/valid/capability-result.complete.json:9) — no non-empty example existsfinding.schema.json(id,verdict,policy_effect,evidenceoverlap), no fixture proves itcapability-result.schema.json#/properties/evidence(:15){"source": "...", "revision": "..."}(fixtures/valid/capability-result.complete.json:8, repeated in every capability-result fixture)reading.schema.json(nosensor/observed_at/producer) orfinding.schema.json. Would need a new minimal citation schema or stay genericcapability-result.schema.json#/properties/completion/properties/evidence(:126)[]completion-evidence.schema.jsonby name/purpose, no fixture proves itinvocation.schema.json#/properties/trusted_inputs(invocation.schema.json:39)kind:{"kind":"issue_body","revision":...,"text":...}(fixtures/valid/invocation.root.json:20-24); this very invocation's owntrusted_inputsadditionally carries{"kind":"capability_result","capability":...,"invocation_id":...}(a pointer, not an embedded document) and{"kind":"continuation_inputs","value":{...}}(free-form)oneOfdiscriminated union keyed onkind; thecapability_resultvariant would$refcapability-result.schema.jsonitself (self-referential composite)invocation.schema.json#/properties/readings(:41)[]reading.schema.json— but still no fixture with content confirms itinvocation.schema.json#/properties/guides(:40){"kind":"persona","path":"..."}(fixtures/valid/invocation.coding.json:27-30) and{"path":"...","revision":"..."}(fixtures/valid/ledger-entry.deferred-dependency.json:25)findingnorreadingmodels a guide; would need a newguide.schema.jsonledger-entry.schema.json#/properties/deferred(ledger-entry.schema.json:39-42)fixtures/valid/ledger-entry.deferred-dependency.json:10-36and.../ledger-entry.deferred-join.jsonembed a complete, schema-conformantinvocationenvelope (every required property present:schema_version, id, parent_id, capability, assignment, artifact, provider, trusted_inputs, guides, readings, allowed_effects, versions, budget)invocation.schema.json. This field should be in scope alongside NOTES.md's six, and it's the only one with proof, not inferenceImportant 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 withfinding.schema.json: it lacksevaluator,expectation,schema_version, and usessubject_revision(string) instead ofsubject(object). Any $ref remediation must not collapsecapability-result.findingsandcompletion-evidence.findingsonto the same target — they are different shapes sharing a field name.Resolver behavior (why $ref would not work today) —
tools/validate.pyROOT = Path(__file__).resolve().parents[1];SCHEMAS/VALID/INVALIDare siblings under it (tools/validate.py:22-25).Draft202012Validator(schema, format_checker=checker)(:71), with schemas collected into a flatdict[stem, schema](:266-274) used only to look up which schema a fixture belongs to — never wired into aRegistry/resolver for cross-file$ref.NOTES.md:105-121:governorinvokestransducerwith single schema file paths (--invocation-schema,--result-schema), not a directory, so a$reftohomeostat://protocol/finding/1.0would not resolve at that boundary either, independent of what Protocol does internally.$reftoday, unchanged, breaks validation both in this repo'stools/validate.py(unresolvable-reference error) and at the governor→transducer boundary.pyproject.toml:6pinsjsonschema[format]>=4.10, uncapped. A registry-based fix (referencing.Registrypassed asDraft202012Validator(schema, registry=..., format_checker=...)) needs jsonschema'sreferencing-based API, stable from roughly 4.18+ — the dependency floor may need raising as part of this change.bundle|generate|resolver|registryacross the checkout; the only hit is an unrelated docstring use of "registry" inevent.schema.json'stypeenum 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.pyalso carries four Python-level semantic-rule functions keyed by schema stem (SEMANTIC_RULESat:247-252:capability-result,completion-evidence,invocation,ledger-entry) that check referential rules JSON Schema can't express (dangling continuationafter, orphaned mutations, lineage vs. budget, etc.). Any cross-field rule the new composite constraints need (e.g. "atrusted_inputsitem of kindcapability_resultnames a real prior invocation") belongs here, not in the schema.Fixture organization rule
fixture_stem = path.name.split(".", 1)[0](:40) must equal aschemas/<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).:289-292).$ref/oneOfrequires, per field, at least one new accepting fixture showing valid nested content and one new rejecting fixture whoseexpect.pointeris the exact JSON-pointer jsonschema reports for a violation inside the referenced part (e.g. afindingsitem missingevaluatorwould report at/findings/0/evaluator).docs/versioning.mdcompatibility classificationschema_version+$idMAJOR.MINOR) vs. component release (VERSION,compatibility.json.release) —docs/versioning.md:6-18.1.0→1.1): additive/loosening only (:37-42).1.x→2.0): adding required properties, tightening, oradditionalProperties: 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).$ref/oneOfonto a schema that hasadditionalProperties: falseand required properties (true offinding,reading,invocation,completion-evidencealike) is a major/tightening change by this rule.docs/versioning.md:73-78andNOTES.md:8-27record the owner's pre-release decision (2026-07-26, amended 2026-07-28) that wire1.0is 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 on1.0, no bump, no migration. The major-change rule only starts binding "from the first tagged release onward."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 onlyREADME.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."1.0under the pre-release exception — there is no<from>-to-<to>pair to name. The correct record of this work under current rules is the existingNOTES.md"Deferred, with a reason" → "$refcomposition between schemas" entry (NOTES.md:97-121), which should be updated/closed in place once the fix lands, rather than a newmigrations/directory being created. If the team wants a migration entry pre-emptively anyway,migrations/1.0-to-1.0-composite-refs/README.mdwould 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) recordstransducer 0.1.5/governor 0.1.2, while this invocation's own dispatch-timeversionsmetadata saysgovernor 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
3971f7428e'}3971f7428e'}3971f7428e'}3971f7428e'}3971f7428e'}3971f7428e'}3971f7428e'}3971f7428e'}3971f7428e'}Findings
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 unrelated9187dfac86on branch main.Requested effects
No deterministic effect requested.
Mutations
None recorded.
Continuation
No continuation requested.
Completion:
none / reachedDelivered by consolidated Protocol PR #5
The architecture decision is embodied by self-contained local
$defs/$refcomposition 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.