From ee9d5f567d696b5da5db2ece59fddf8927c6469b Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 15:47:39 -0400 Subject: [PATCH 01/15] spec: portable-core-contracts --- work/portable-core-contracts/spec.md | 771 +++++++++++++++++++++++++++ 1 file changed, 771 insertions(+) create mode 100644 work/portable-core-contracts/spec.md diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md new file mode 100644 index 0000000..ba1853d --- /dev/null +++ b/work/portable-core-contracts/spec.md @@ -0,0 +1,771 @@ +--- +intent-blob: 3ed8bb434c096ec126d680019a9491ab8a113e31 +drafted: 2026-08-28 +--- + +# Spec: portable core contracts + +Define one small, vendor-neutral JSON contract family and a pure validator for it. +The validator checks canonical bytes, document shape, and relationships among +documents supplied by its caller. It never reads Git, runs an adapter, grants +authority, or changes the live ystack profile. + +## Requirements + +- **R1 — one bounded concern.** This initiative owns canonical records and pure + validation only. It does not resolve a profile from Git, execute a fake or real + adapter, run the 2×2 matrix, authenticate a record, grant a permission, publish a + change, or activate a profile. The current Claude Code, Codex, and GitHub path + keeps working unchanged. +- **R2 — seven document kinds.** Version 1 accepts exactly `stage_request`, + `stage_result`, `adapter_manifest`, `profile`, `resolved_profile`, + `adapter_contract_test_inventory`, and `adapter_contract_test_result`. These are + the only top-level core documents. Evidence stays inside a stage result. Policy, + selection, qualification, grant, gate, and inventory-acceptance records stay + outside the core and enter only through typed immutable references. +- **R3 — strict canonical JSON.** Every document is UTF-8 JSON whose complete bytes + equal pinned jq 1.6 invoked as `jq -S -c .`, followed by one line feed. Reject a + BOM, invalid UTF-8, + duplicate keys, alternate escaping or whitespace, floats, negative integers, + `null`, unknown fields, and non-canonical bytes. The full canonical envelope is + what a document digest covers. +- **R4 — fixed resource limits.** Reject a document over 1,048,576 bytes, nesting + deeper than 32, an object or array with more than 256 members, a decoded string + over 8,192 UTF-8 bytes, or an integer outside `0..2147483647`. Arrays that mean a + set are sorted by their stable ID and contain no duplicates. Arrays whose order + has meaning are named as ordered lists in this spec. +- **R5 — explicit versioning and inert extensions.** Every envelope has exactly + `schema_version: 1`, `kind`, `id`, `body`, and optional `extensions`. Unknown + kinds, versions, and core fields fail closed. Extensions are allowed only at the + top level. Their keys use a lowercase reverse-domain prefix and `/leaf`; each + value is an object. They are preserved in canonical bytes but cannot fill a core + field, add a role/capability/permission, change a result, choose a tool or policy, + or reach an executor or publisher. Any new core field, including an optional + field, or changed enum meaning requires a new major schema version. +- **R6 — references are claims, not trust.** The shared reference shapes in Design + use full digests and safe logical IDs. Pure validation checks their syntax and + equality only. A valid ref does not prove that content or a Git object exists, + that a repository ID maps to the right repository, that an actor is authentic, + or that a policy, selection, qualification, grant, gate decision, evidence + verdict, or inventory acceptance is authoritative. +- **R7 — one operation per request.** A stage request names one resolved binding, + one v1 capability, its exact effective permission set, and the capability's exact + argument object. It also binds the initiative, workflow, stage, task class, + target/source/input claims, risk claim, profile and environment, finish condition, + verification instructions, required evidence kinds, and any caller-supplied + selection, qualification, grant, or gate-decision refs. Retry attempts retain the + same exact request document ref by later orchestrator policy; this pure validator + checks one attempt and does not enforce retry sequence. A request cannot express merge, approval, bypass, human + impersonation, policy activation, arbitrary command execution, or generic network + access. +- **R8 — total result rules.** A stage result binds one exact request and one + attempt. It records the controller, terminal status, whether the operation ran, + the outcome when allowed, reason and diagnostics, expected/observed identities, + outputs, evidence, actual performer and capability when executed, actual + execution facts, and ordered times. Every status/outcome combination is defined + in Design; all others fail. `completed` means the operation finished, not that it + passed. +- **R9 — actual execution facts stay separate from requested config.** An executed + result records its actual adapter/package/config, environment, tools, and either + deterministic or model execution metadata. Every possibly hidden actual model + fact—provider, model, snapshot, effort, prompt, skills, tools, trace, usage, and + cost—uses the same typed availability union. Missing facts are `unavailable` with + a reason, never guessed or copied from the profile. An unexecuted result + cannot carry a performer, used capability, execution metadata, evidence, or a + successful output. +- **R10 — evidence is exact and non-transferable.** Each evidence record carries the + complete `stage_request` document ref, attempt ID/number, finish condition, every + enclosing output ref, resolved profile, qualification scope when present, + environment, performer, actual binding/package/config, verification instructions, + proof bytes, kind, and verdict. A completed result covers exactly the evidence + kinds requested before execution. Evidence from another request body, attempt, + output, profile, environment, or instruction version cannot be replayed as current + proof even if a stable request ID was reused. +- **R11 — risk and authority references stay separate.** Risk is a claim containing + a tier, reason IDs, policy ref, and required gate refs. Selection, repository + context, qualification, grant, policy, authority, gate decision, and inventory + acceptance use distinct `scope_ref.purpose` values and cannot substitute for one + another. There is no core `trusted`, `authorized`, `qualified`, `approved`, or + `active` boolean. +- **R12 — offer, request, grant, and use never collapse.** A manifest offers roles, + capabilities, permissions, and execution kinds. A profile requests a subset. A + resolved profile records the resolver's claimed deterministic result. An external + accepted policy/permission record may grant. An executed result records actual + use. Validation never infers one step from another. +- **R13 — protected roles stay separate.** Producer, verifier, reviewer, and + publisher bindings are pairwise different in binding ID, adapter instance, + principal, and execution boundary. Authority record IDs and scope digests also + differ when present. The same implementation may serve more than one role only + through those distinct bindings. Producer has no publish permission; verifier has + no model or forge-write permission; reviewer is exact-change read-only; publisher + has no model or candidate-execution permission; a human decision is not an + adapter role or capability. +- **R14 — every v1 capability is closed.** The registry in Design is exhaustive. + Every capability has one role, one outcome family, an exact argument shape, exact + permissions, and a closed allowed evidence set with required members. Unknown or + extra argument fields fail. + There is no wildcard, prefix match, alias, generic shell/argv/env/URL/API, + credential read, generic filesystem write, force/delete, merge, approval, bypass, + deploy, or policy-activation capability. +- **R15 — profile provenance is resolver-produced.** A profile contains requested + bindings and immutable refs, but no source ref, selection, trust root, grant, + qualification, gate, or activation claim. `resolved_profile` carries the profile + and manifest document refs plus source-value, selection, and repository-context + claims produced by the later resolver. Core validation compares supplied + documents and subsets but never reads a repository or accepts self-declared + profile provenance. +- **R16 — test expectations and observations are different records.** The inventory + alone owns expected phase, verdict/error, equivalence group, fixture digest, and + assertion IDs. The result contains observations only and binds the exact inventory + plus an external inventory-acceptance ref. Pure validation checks one-for-one case + and assertion sets and a correctly derived overall outcome. It does not execute a + case, authenticate acceptance, or treat a structurally valid pass report as proof. +- **R17 — one pure validator boundary.** `core/v1/contracts.jq` is the only + executable source of shapes, enums, registries, and relational rules. + `scripts/core-contract.sh` exposes only the four pure commands in Design. It does + not read Git, physical repository mappings, raw fixtures, network, environment + config, executables, or credentials. Success is exit 0 with empty stdout. Failure + is nonzero, emits one stable error code first, and never echoes untrusted input. +- **R18 — adversarial proof and delivery.** Hermetic tests cover every valid kind, + every capability argument shape, and the rejection matrix in Design. Tests derive + mutations from small valid baselines; they do not run an adapter or use a network. + New load-bearing files enter `ci/required-files.txt`; README states the contract + and that it is not live-wired. CI runs the suite with checksum-pinned jq 1.6. + An operator-driven implementation may update `.github/workflows/ci.yml`; an + unattended implementation must submit that constitution change under + `proposals/` for operator application. +- **R19 — normal review size.** The implementation budget is at most 220 net lines + for `contracts.jq`, 45 for the wrapper, 100 for table-driven tests, and 35 for + docs, manifest, and CI: 400 net lines total. The plan must validate these estimates + before code. If it cannot meet them without dropping a requirement, return to G2 + and reduce or split v1. This spec does not accept the old #154 size exception. + +## Design + +### Canonical envelope and primitive rules + +Core IDs match `^[a-z0-9][a-z0-9._:-]{0,127}$`. SHA-256 values are 64 lowercase +hex characters. Versions are exact IDs, never ranges. Optional fields are omitted; +`null` is always invalid. + +An extension key has two or more lowercase DNS labels, each 1–63 characters with +no leading or trailing hyphen, then `/`, then a 1–128 character lowercase leaf made +from letters, digits, `.`, `_`, or `-`. For example, `com.example/trace` is valid; +`trace`, `Com.example/x`, and `com..example/x` are not. + +The wrapper checks the byte limit before parsing. It canonicalizes once with the +pinned jq, compares the emitted bytes with the original, then applies the depth, +member, string, integer, shape, and relational checks. The byte comparison makes a +duplicate key or alternate escape non-canonical even though jq would otherwise keep +only the final decoded key. Regression tests protect that boundary. + +### Normative schema notation and shared shapes + +All objects below are exact: a field not listed is invalid. `T?` means the field may +be omitted; `set(key,min..max)` is a JSON array sorted by `key`, unique by that +key, and within the stated cardinality; `list(min..max)` preserves order. +`present` is exactly `{state:"present",value:T}` or `{state:"absent"}`. +`availability` is exactly one of: + +```text +{state:"recorded",value:T,source_ref:content_ref} +{state:"computed",value:T,source_ref:content_ref} +{state:"unavailable",reason_id:ID} +{state:"not-applicable"} +``` + +`ShortText` is 1–1,024 decoded UTF-8 bytes and is always untrusted prose. `Time` is +the UTC value defined below. `Version` is an exact core ID. `MediaType` is a +lowercase media type matching +`^[a-z0-9][a-z0-9!#$&^_.+-]{0,62}/[a-z0-9][a-z0-9!#$&^_.+-]{0,62}$`. +`GitOID` is 40 +lowercase hex characters for SHA-1 or 64 for SHA-256. `RepoPath` follows the lexical +rules below. `TopicRef` is `refs/heads/` plus slash-separated 1–64 character +segments made from lowercase letters, digits, `.`, `_`, or `-`; no segment is `.`, +`..`, ends in `.` or `.lock`, or contains `..`. `IdempotencyKey` is an `ID`. +`ExtensionPrefix` is the reverse-domain portion of the extension-key grammar. +`canonical-sha256` as a set key means SHA-256 of the nested value's canonical JSON; +`value` means the primitive string itself. + +The adapter-role registry is exactly `producer`, `verifier`, `reviewer`, `forge`, +`ci`, `execution`, `identity`, and `publisher`. Actor roles may also be `operator`, +`manager`, `orchestrator`, or `observer`; those four can request/report but cannot +be an adapter binding or operation performer. Human approval is not an actor role. +An `operator` actor ref is attribution only and never a human decision record. + +| Shape | Exact object | +|---|---| +| `document_ref(K?)` | `{schema_version:1,kind:DocumentKind,id:ID,sha256:SHA256}`; when `K` is supplied, `kind=K` | +| `git_revision_ref` | `{repository_id:ID,hash_algorithm:"sha1"|"sha256",commit_id:GitOID}` with matching hash length | +| `git_object_ref` | `{revision:git_revision_ref,path:RepoPath,object_type:"blob"|"tree",object_id:GitOID,mode:"100644"|"100755"|"040000"}`; object ID length matches `revision.hash_algorithm`; tree requires `040000`, blob requires `100644|100755` | +| `content_ref` | `{content_id:ID,media_type:MediaType,sha256:SHA256}`; the ID is not a URL or host path | +| `artifact_ref` | `{type:"git-object",value:git_object_ref}` or `{type:"content",value:content_ref}` | +| `evidence_ref` | `{stage_result_ref:document_ref(stage_result),evidence_id:ID}` | +| `input_value_ref` | tagged `{type:"artifact",value:artifact_ref}` or `{type:"document",value:document_ref}`; prior evidence has its own field | +| `claim_value_ref` | tagged `{type:"artifact",value:artifact_ref}`, `{type:"document",value:document_ref}`, `{type:"git-revision",value:git_revision_ref}`, `{type:"scope",value:scope_ref}`, or `{type:"environment",value:environment_ref}` | +| `source_value_ref` | `{source:git_object_ref,value_format:"raw-bytes"|"canonical-json",value_sha256:SHA256}` | +| `scope_subject_ref` | tagged `{type:"document",value:document_ref}` or `{type:"artifact",value:artifact_ref}` | +| `scope_ref(P?)` | `{purpose:ScopePurpose,decision_record_ref:content_ref,subject_ref:scope_subject_ref,scope_sha256:SHA256}`; when `P` is supplied, `purpose=P` | +| `actor_ref` | `{role:ActorRole,implementation_id:ID,implementation_version:Version,adapter_instance_id:ID,principal_id:ID,execution_boundary_id:ID,authority_ref?:scope_ref(authority)}` | +| `environment_ref` | `{environment_id:ID,fingerprint_sha256:SHA256}` | +| `tool_ref` | `{tool_id:ID,tool_version:Version,package_ref:artifact_ref,config_sha256:SHA256}` | +| `change_ref` | `{repository_id:ID,base:git_revision_ref,head:git_revision_ref,delta_ref:content_ref}`; both revisions use `repository_id` | + +`ScopePurpose` is exactly `selection`, `repository-context`, `qualification`, +`grant`, `policy`, `authority`, `gate-requirement`, `gate-decision`, `inventory-acceptance`, +`config-contract`, `output-contract`, `allowed-delta`, `verification-plan`, +`review-policy`, `check-set`, `environment-policy`, `publisher-policy`, +`finish-condition`, or `verification-instructions`. A field expecting one purpose +rejects every other purpose. + +A `RepoPath` has no leading or trailing slash, empty segment, `.` or `..` segment, +backslash, NUL, or ASCII control character. These are lexical checks only. The +profile resolver owns physical repository mapping, object existence, mode, symlink +containment, replacement-object disabling, and source provenance. + +For `source_value_ref`, `raw-bytes` means SHA-256 of the exact Git object payload; +`canonical-json` means SHA-256 of the complete canonical JSON bytes and is valid +only for a blob. Core validates the tag, hash, and blob restriction but never reads +those bytes. + +### Exact nested records + +- `model_request` is `{provider_id:ID,model_id:ID,effort_id:ID}`. +- `profile_binding` is exactly `{binding_id:ID,role:AdapterRole, + manifest_ref:document_ref(adapter_manifest),execution_kind:"model"|"deterministic", + adapter_instance_id:ID,principal_id:ID,execution_boundary_id:ID, + authority_ref?:scope_ref(authority),package_ref:git_object_ref, + config_ref?:git_object_ref,prompt_ref?:git_object_ref, + skill_refs:set(canonical-sha256,0..256), + requested_tool_refs:set(tool_id,0..256),model_request?:model_request, + requested_capabilities:set(value,1..256), + requested_permissions:set(value,1..256)}`. `model_request` and + `prompt_ref` are required exactly when `execution_kind=model`. Tool refs inside a + manifest or profile require a `git-object` package ref. +- `resolved_binding` is exactly `{binding:profile_binding, + adapter_implementation:{id:ID,version:Version}, + manifest_source:source_value_ref,package_source:source_value_ref, + config_source?:source_value_ref,prompt_source?:source_value_ref, + skill_sources:set(canonical-sha256,0..256), + tool_sources:set(canonical-sha256,0..256)}`. Optional/source set + presence and source object refs exactly match the binding refs they claim to resolve. +- `named_input` is `{input_id:ID,value:input_value_ref}`. +- `risk_claim` is `{tier:{namespace:"core",name:"routine"|"high"|"bootstrap"} + |{namespace:ExtensionPrefix,name:ID},reason_ids:set(value,1..256), + policy_ref:scope_ref(policy),required_gate_refs:set + (scope_sha256,0..256)}`. A non-core tier is inert. +- `operation` is `{role:AdapterRole,binding_id:ID,capability_id:CapabilityID, + permissions:set(value,1..256),arguments:CapabilityArgs}` where the + argument schema is selected only by `capability_id` from the registry below. +- `Outcome` is exactly `{family:"change",value:"changed"|"no-change"| + "inconclusive"}`, `{family:"check",value:"passed"|"failed"|"inconclusive"}`, + or `{family:"advisory",value:"proceed"|"refine"|"drop"|"inconclusive"}`. +- `TerminalStatus` is exactly `completed`, `skipped`, `stale`, `blocked`, `failed`, + or `cancelled`. `EvidenceKind` is exactly `deterministic`, `behavioral`, + `architecture`, or `independent-review`. +- `reason` is `{reason_id:ID,summary?:ShortText}`. Summary text never drives a tool, + status, label, permission, or gate. +- `output_record` is `{output_id:ID,purpose:"subject"|"auxiliary"|"release", + ref:input_value_ref}`. Output sets sort by `output_id`. +- `actual_binding` is `{binding_id:ID, + adapter_implementation:{id:ID,version:Version}, + manifest_ref:document_ref(adapter_manifest), + package_ref:git_object_ref,config_ref?:git_object_ref,execution_kind:"model"| + "deterministic",adapter_instance_id:ID,principal_id:ID, + execution_boundary_id:ID,authority_ref?:scope_ref(authority)}`. +- `usage_value` is `{input_tokens:Int,output_tokens:Int,cache_read_tokens:Int, + cache_write_tokens:Int}`. `cost_value` is `{currency_id:ID,microunits:Int}`. +- `execution_metadata` is exactly `{kind:"model"|"deterministic", + provider:availability,model:availability,snapshot:availability, + effort:availability,prompt:availability, + skills:availability(canonical-sha256,0..256)>, + tools:availability(tool_id,0..256)>,trace:availability, + usage:availability,cost:availability}`. Deterministic + execution requires provider/model/snapshot/effort/prompt/skills to be + `not-applicable`; model execution requires each of those fields to be recorded, + computed, or unavailable, never not-applicable. +- `evidence_record` is exactly `{evidence_id:ID, + request_ref:document_ref(stage_request),attempt_id:ID,attempt_number:Int, + finish_condition_ref:scope_ref(finish-condition), + resolved_profile_ref:document_ref(resolved_profile), + qualification_ref?:scope_ref(qualification),environment_ref:environment_ref, + performer:actor_ref,actual_binding:actual_binding, + verification_instruction_ref:scope_ref(verification-instructions), + outputs:set(output_id,0..256),delta_ref?:content_ref, + kind:EvidenceKind, + verdict:"passed"|"failed"|"inconclusive",proof_ref:content_ref}`. +- `stale_selector` is `{kind:"target"|"source"|"base"|"selection"| + "repository-context"|"qualification"|"grant"|"environment"| + "resolved-profile"}`, `{kind:"input",input_id:ID}`, or + `{kind:"gate-decision",scope_sha256:SHA256}`. It can name only a baseline that + exists as a field or set member in the request. +- `stale_comparison` is `{selector:stale_selector, + expected:present,observed:present}`. Validator + derives `expected` from the selected request baseline and requires exact equality. + Observed uses the same semantic type when present: same document kind; same + artifact variant; same Git repository/hash algorithm/path/object type/mode, or + same content ID/media type; same document kind/schema/ID; same scope purpose and + decision-record/content identity plus subject logical identity; or same + environment ID. Presence or canonical value must differ. +- `test_assertion_result` is `{assertion_id:ID,passed:Boolean}`. +- `test_case` is `{case_id:ID,phase:TestPhase,fixture_ref:artifact_ref, + expected_status:"accepted"|"rejected"|"transport-failed", + expected_error_id?:ID,equivalence_group?:ID, + assertion_ids:set(value,1..256)}`. Error is forbidden for `accepted` and + required otherwise. +- `test_observation` is `{case_id:ID,phase:TestPhase, + observed_status:"accepted"|"rejected"|"transport-failed"|"inconclusive", + observed_error_id?:ID,produced_document_refs:set(sha256,0..256), + assertions:set(assertion_id,1..256)}`. Error is forbidden + for accepted and required otherwise. + +### Exact top-level document bodies + +The envelope `id` is respectively the adapter ID, profile ID, resolved-profile ID, +stable request ID, result ID, inventory ID, or contract-test-result ID. Body fields +do not repeat it. + +- `adapter_manifest.body` is `{adapter_version:Version,protocol_version: + "core-stage/v1",package_ref:git_object_ref, + offered_roles:set(value,1..8), + offered_execution_kinds:set<"model"|"deterministic">(value,1..2), + offered_capabilities:set(value,1..256), + offered_permissions:set(value,1..256), + offered_tool_refs:set(tool_id,0..256), + config_contract_ref?:scope_ref(config-contract)}`. +- `profile.body` is `{profile_version:Version, + bindings:set(binding_id,1..8)}` with at most one binding per + adapter role. It contains no source, selection, trust, grant, qualification, + gate, or activation field. +- `resolved_profile.body` is `{profile_ref:document_ref(profile), + profile_source:source_value_ref,selection_ref:scope_ref(selection), + repository_context_ref:scope_ref(repository-context), + bindings:set(binding.binding_id,1..8)}`. Profile source uses + `canonical-json` and its value digest equals `profile_ref.sha256`. +- `stage_request.body` is `{initiative_id:ID,workflow_id:ID,stage_id:ID, + task_class_id:ID,requested_by:actor_ref,target_ref:git_revision_ref, + source:present,base:present, + inputs:set(input_id,0..256), + prior_evidence_refs:set(stage_result_ref.sha256+evidence_id,0..256),risk:risk_claim, + resolved_profile_ref:document_ref(resolved_profile), + selection_ref:scope_ref(selection), + repository_context_ref:scope_ref(repository-context), + qualification_ref?:scope_ref(qualification),grant_ref?:scope_ref(grant), + gate_decision_refs:set(scope_sha256,0..256), + environment_ref:environment_ref,operation:operation, + finish_condition_ref:scope_ref(finish-condition), + verification_instruction_ref:scope_ref(verification-instructions), + required_evidence_kinds:set(value,1..4),requested_at:Time}`. +- `stage_result.body` is `{request_ref:document_ref(stage_request), + resolved_profile_ref:document_ref(resolved_profile),attempt_id:ID, + attempt_number:Int,reported_by:actor_ref,executed:Boolean,status:TerminalStatus, + outcome?:Outcome,reason?:reason, + stale_comparisons?:set(canonical-sha256,1..256), + outputs:set(output_id,0..256),delta_ref?:content_ref, + diagnostics:set(content_id,0..256),performer?:actor_ref, + used_capability?:CapabilityID,actual_binding?:actual_binding, + execution_metadata?:execution_metadata, + evidence:set(evidence_id,0..256),started_at?:Time, + finished_at?:Time,recorded_at:Time}`. Presence follows the total status table. +- `adapter_contract_test_inventory.body` is `{inventory_version:Version, + cases:set(case_id,1..256)}`. +- `adapter_contract_test_result.body` is `{inventory_ref: + document_ref(adapter_contract_test_inventory),inventory_acceptance_ref: + scope_ref(inventory-acceptance),runner:actor_ref,environment_ref:environment_ref, + execution_metadata:execution_metadata,started_at:Time,finished_at:Time, + recorded_at:Time,overall:"passed"|"failed"|"inconclusive", + observations:set(case_id,1..256)}`. Runner role is `verifier`, + execution metadata is deterministic, and `started_at <= finished_at <= recorded_at`. + +The resolver must not copy a profile-declared source because no such field exists. +`validate-profile-set` recomputes document refs from supplied canonical files and +checks every binding, source presence, subset, and equality rule above. It cannot +prove any Git read. + +### Status, outcome, and evidence rules + +Outcome is one tagged union: + +- `change`: `changed`, `no-change`, or `inconclusive`; +- `check`: `passed`, `failed`, or `inconclusive`; +- `advisory`: `proceed`, `refine`, `drop`, or `inconclusive`. + +An advisory is data for a later gate. It is never approval or a gate decision. + +| Status | Executed | Outcome and allowed records | +|---|---:|---| +| `completed` | true | outcome required; full requested evidence required | +| `skipped` | false | no outcome; reason required; no performer/evidence/output | +| `stale` | false | no outcome; reason and one or more differing stale comparisons required; no performer/evidence/output | +| `blocked` | false | no outcome; reason required; diagnostics allowed; no performer/evidence/output | +| `failed` | false | no outcome; machinery reason and diagnostics required; no performer/evidence/output | +| `failed` | true | only an inconclusive outcome; reason, diagnostics, and attempt evidence required; no successful subject output or delta | +| `cancelled` | false | no outcome; reason required; no performer/evidence/output | +| `cancelled` | true | only an inconclusive outcome; reason and attempt evidence required; no successful subject output or delta | + +Outcome is present exactly when `executed=true`. Performer, used capability, +actual binding, execution metadata, start time, and finish time are also present +exactly when executed. Reason is required for every non-completed status and every +inconclusive outcome; it is forbidden for a completed non-inconclusive outcome. +`stale_comparisons` is present exactly for `stale`; every expected value is derived +from its request selector, every observed value is type-compatible, and every pair +differs. Diagnostics are non-empty for +`failed`, may be non-empty for `blocked` or `cancelled`, and are empty otherwise. + +Every result has `recorded_at`. An unexecuted result omits start/finish times and +requires `request.requested_at <= recorded_at`. An executed result requires +`requested_at <= started_at <= finished_at <= recorded_at`. Times are real UTC +calendar values in exact second-level `YYYY-MM-DDTHH:MM:SSZ` form. Attempt number +must be at least 1. Monotonic attempt numbering, duplicate delivery, and retry +continuity require history and belong to the durable orchestrator. + +Only `change/changed` may have subject or release outputs and a delta ref. It +requires at least one subject output and the delta. +`change/no-change` and `change/inconclusive` forbid both. Auxiliary artifacts are +allowed only on completed outcomes and are bound by every evidence record. +Diagnostics never satisfy requested evidence. + +Evidence kinds are exactly `deterministic`, `behavioral`, `architecture`, and +`independent-review`; verdicts are `passed`, `failed`, and `inconclusive`. A +completed result has at least one evidence record for every requested kind, no +unrequested kind, and unique evidence IDs. `check/passed`, `change/changed`, +`change/no-change`, and non-inconclusive advisory outcomes require all requested +evidence to pass. For a completed result, failed evidence takes precedence: a check-family result is +`check/failed`; a change/advisory result is its family's `inconclusive`. If there is +no failed evidence but at least one inconclusive verdict, every family uses its +`inconclusive` value. `check/failed` therefore requires at least one failed verdict +and may retain inconclusive verdicts. A family-inconclusive outcome requires at +least one failed or inconclusive verdict. No true verdict is discarded. + +For `failed|cancelled + executed=true`, evidence is non-empty, its kinds are a +subset of the request's required kinds, every verdict is `failed` or +`inconclusive`, at least one is non-passing, and its output set is empty. It records only the interrupted attempt and cannot satisfy a +later completed result. An unexecuted result has an empty evidence set. + +`validate-stage-run` recomputes the complete request document ref and requires it +to equal the result and every nested evidence `request_ref`. Evidence attempt values +equal the enclosing result. Finish condition, profile, qualification presence, +environment, verification instructions, performer, actual binding, every output +record, and delta presence/value also equal the request/result/binding values exactly. Every evidence +record on a completed result covers the complete output set. Prior-stage evidence +appears only in `request.prior_evidence_refs`; its body is never copied into the new +result. + +For an executed result, `performer` matches the selected resolved binding's role, +implementation, instance, principal, and boundary. `actual_binding` matches that +binding's manifest/package/config and execution kind. Execution metadata kind +matches it too. All nested evidence repeats those exact performer, binding, and +environment values. These are equality checks over claims, not identity proof. + +Every possibly hidden model fact uses `availability` as defined above. A profile +model request is desired configuration only and cannot fill an actual result field. + +### Capability, permission, and argument registry + +The permission registry is the union of the full IDs in this table plus the +conditional `core.perm.model.invoke.v1`. No short name is an alias. Every argument +object has exactly the fields and types shown. `D`, `B`, `A`, and `R` mean +deterministic, behavioral, architecture, and independent-review evidence. +`core.perm.record.read.v1` reads only canonical core documents named by supplied +`document_ref`s; `core.perm.content.read.v1` reads only bytes named by supplied +`content_ref`s. Neither permits path, directory, database, generic network, or +credential reads. + +| Role / capability | Exact arguments | Exact base permissions | Outcome / allowed evidence; required | +|---|---|---|---| +| producer / `core.harness.plan.v1` | `{output_contract_ref:scope_ref(output-contract)}` | `core.perm.target.read.v1`, `core.perm.scratch.write.v1` | change / `{A}`; A | +| producer / `core.harness.produce.v1` | `{deliverable_kind:"git-patch"|"structured-artifact",allowed_delta_ref:scope_ref(allowed-delta)}` | `core.perm.target.read.v1`, `core.perm.scratch.write.v1` | change / `{D}`; D | +| verifier / `core.verify.run.v1` | `{verification_plan_ref:scope_ref(verification-plan),network_mode:"deny"}` | `core.perm.target.read.v1`, `core.perm.execution.candidate.v1`, `core.perm.evidence.write.v1` | check / `{D,B,A}`; D | +| reviewer / `core.review.check.v1` | `{change_ref:change_ref,review_policy_ref:scope_ref(review-policy)}` | `core.perm.target.read.v1`, `core.perm.evidence.write.v1` | check / `{R}`; R | +| reviewer / `core.review.advise.v1` | `{change_ref:change_ref,review_policy_ref:scope_ref(review-policy)}` | `core.perm.target.read.v1`, `core.perm.evidence.write.v1` | advisory / `{R}`; R | +| forge / `core.forge.observe.v1` | `{observation_kind:ForgeObservation,subject:ForgeSubject}` | `core.perm.forge.read.v1` | check / `{D}`; D | +| ci / `core.ci.observe.v1` | `{repository_id:ID,commit_ref:git_revision_ref,check_set_ref:scope_ref(check-set),required_only:true}` | `core.perm.ci.read.v1` | check / `{D}`; D | +| execution / `core.execution.provision.v1` | `{environment_spec_ref:scope_ref(environment-policy),input_snapshot_ref:artifact_ref,network_mode:"deny",tool_refs:set(tool_id,0..256)}` | `core.perm.execution.provision.v1` | check / `{D}`; D | +| identity / `core.identity.resolve.v1` | `{subject_actor_ref:actor_ref,purpose:"performer"|"verifier"|"reviewer"|"publisher"|"observer"}` | `core.perm.identity.read.v1` | check / `{D}`; D | +| publisher / `core.publish.branch-bounded.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),topic_ref:TopicRef,expected_old_tip:present,new_commit_ref:git_revision_ref,delta_ref:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.topic-ref.write.v1` | change / `{D}`; D | +| publisher / `core.publish.change-request.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),source_result_ref:document_ref(stage_result),head_ref:{name:TopicRef,commit:git_revision_ref},base_ref:git_revision_ref,title_ref:content_ref,body_ref:content_ref,draft:Boolean,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.change-request.write.v1` | change / `{D}`; D | +| publisher / `core.publish.comment.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),subject:{kind:"issue"|"change-request",id:ID},source_result_ref:document_ref(stage_result),body_ref:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.comment.write.v1` | change / `{D}`; D | +| publisher / `core.publish.status.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),commit_ref:git_revision_ref,source_result_ref:document_ref(stage_result),context_id:ID,state:"success"|"failure"|"neutral",details_ref?:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.status.write.v1` | change / `{D}`; D | + +`ForgeObservation` is exactly `repository-identity`, `default-branch`, +`change-request-state`, `approval-state`, `head-base-identity`, `branch-controls`, +or `code-owner-controls`. `ForgeSubject` is `{repository_id:ID}` for repository, +default-branch, branch-control, and code-owner observations, and +`{repository_id:ID,change_id:ID}` for the other three. There is no query, path, URL, +or arbitrary payload. + +Both reviewer capabilities require the same exact `change_ref`; they differ only in +outcome family. Advice about a non-change subject belongs to a later manager or +orchestrator contract, not the reviewer role. + +Scope-ref arguments carry identity and scope only. They do not embed or expose the +referenced bytes, and v1 defines no command that dereferences them. A later trusted +caller/adapter may use an accepted, separately validated policy or instruction +record, but it cannot reinterpret that record as shell, argv, environment, URL, or +an extra capability under this schema. + +Direct work-subject refs equal the request target repository: reviewer changes, +forge subjects, CI commits, a Git execution snapshot, and publisher repository, +head/base/new commits. Change base/head revisions share that repository. Policy, +adapter, package, config, prompt, skill, and tool refs may live in other logical +repositories and instead must match the resolved profile/binding claims. Publisher title, +body, and details refs have media type `text/plain`. +`source_result_ref` appears in the request's named document inputs; pure validation +checks the ref linkage, while the later publisher verifies that the projected bytes +are an allowed output of that result. + +`expected_old_tip` is `absent` or a present commit from the same repository. The +later publisher verifies real branch policy, current tip, ancestry, actual delta, +credential scope, and atomic compare-and-swap. Branch arguments cannot express +force, delete, merge, or bypass. Change-request arguments can only ensure/open a +request; they cannot close, merge, approve, or assign a reviewer. Status is a +projection, not a gate result. A generic label write is deliberately absent because +labels may carry gate state; a later fixed projection needs a new core version. + +Before any grant or execution, the authenticated publisher must validate the exact +publisher-policy record. For status, it queries the forge controls and rejects a +context used by any required, protected, approval, merge, or production gate. For +change-request and comment text, it dereferences the fixed source output and rejects +mention-like control tokens, provider/bot directives, a line whose first non-space +character is `/`, and control characters. It never rewrites unsafe bytes into safe +ones. If the adapter cannot prove these checks, the capability remains ungranted +and cannot execute. Branch, change-request, and status publishers use their exact +`core.perm.forge.read.v1` immediately before the fixed write and abort on an unknown +or moved control state. This narrows but cannot eliminate the read/write race; later +server-side controls remain required. Core validation checks only the policy ref and +closed proposal shape; it does not claim these external checks already happened. + +Only producer and reviewer bindings may use `execution_kind: model`. Their effective +permission set adds exactly `core.perm.model.invoke.v1`; every other role is +deterministic. Request evidence kinds are a subset of the capability's closed +allowed set and include every required kind. Only either review capability may +produce `independent-review` evidence. For every supplied profile set and stage run, +validation enforces: + +```text +supplied manifest document refs equal the profile binding manifest-ref set +manifest capability roles are a subset of its offered roles +resolved binding-ID set equals the profile binding-ID set exactly +binding role and execution kind are offered by that manifest +binding package ref equals the manifest package ref +binding config ref is allowed only when the manifest has a config-contract ref +every requested capability belongs to the binding role in the core registry +binding requested tools are a subset of manifest offered tools +profile requested capabilities are a subset of manifest offers +profile requested permissions equal the exact union required by its capabilities +profile requested permissions are a subset of manifest offers +resolved binding.binding equals the profile binding +resolved adapter implementation ID/version equal manifest envelope ID/body version +profile/manifest source-value canonical-json digests equal their document refs +package/config/prompt/skill/tool source presence and object refs equal the binding refs +request resolved-profile ref equals the recomputed supplied resolved-profile ref +request selection/repository-context refs equal supplied resolved-profile body refs +operation binding ID and role equal the selected resolved binding +operation capability belongs to its resolved binding +execution-provision argument tools are a subset of resolved binding requested tools +operation permissions equal that capability's effective permissions +result request/resolved-profile refs equal the supplied request/profile documents +executed result used capability equals the request capability +executed result outcome family equals the request capability registry family +executed result metadata kind equals the resolved binding execution kind +recorded/computed actual tools are a subset of resolved binding requested tools +executed result performer and actual binding equal the resolved binding +performer implementation ID/version and authority presence/value equal that binding +all evidence performer/binding/environment values equal the result and request +``` + +The core validates only the shape of a publisher proposal and these document +relations. It does not make the write safe or authorized. A valid status proposal +can never stand in for a gate decision. + +### Adapter-contract-test records + +`TestPhase` is exactly `parse`, `document`, `profile-set`, `stage-run`, +`adapter-run`, or `matrix`. Expected observation is `accepted`, `rejected`, or +`transport-failed`, with an exact stable error ID when applicable. The inventory +case set and each assertion-ID set are non-empty, sorted, and unique. + +Each result observation has case ID, phase, observed status/error, produced document +refs when any, and one boolean result for every inventory assertion ID. It has no +expected fields. Missing, extra, duplicate, relabelled, or phase-mismatched cases or +assertions fail relational validation; missing execution never becomes a smaller +case set. The result is `inconclusive` if any observation is explicitly +`inconclusive`. Otherwise it is `passed` only when every observed status/error and +assertion matches the inventory, including an expected `transport-failed` case; any +complete mismatch or false assertion derives `failed`. + +For a contract-test result, actual tools and trace must be recorded or computed. +If either is unavailable or not-applicable, every observation is `inconclusive` and +the overall result is `inconclusive`; the case set is still complete. + +`inventory_acceptance_ref` has purpose `inventory-acceptance` and its subject equals +the inventory document ref. `EXPECTED_ACCEPTANCE_SCOPE` is a file containing exactly +one canonical `scope_ref(inventory-acceptance)` object without a document envelope; +it uses the same jq canonical bytes, limits, and extension-free shared-shape checks. +`validate-test-records` receives that caller-owned trust-context file and requires +object equality with the result field. This proves linkage only. The later +adapter-test runner must independently +execute every case, revalidate every produced document, verify Git through the +resolver-owned boundary, and bind real runner evidence. Copying expected fields into +observations can still form a structurally valid lie; core validation never calls it +proof. + +### Pure validator interface and errors + +`scripts/core-contract.sh` has exactly these commands: + +```text +validate-document DOCUMENT +validate-profile-set PROFILE RESOLVED_PROFILE MANIFEST... +validate-stage-run REQUEST RESOLVED_PROFILE RESULT +validate-test-records INVENTORY RESULT EXPECTED_ACCEPTANCE_SCOPE +``` + +The relational commands receive every document they compare. They recompute each +`document_ref` from the supplied canonical bytes. No command accepts a repository, +physical root, executable, command line, environment map, URL, or credential. + +Stable first-token errors are `E_USAGE`, `E_RUNTIME`, `E_PARSE`, `E_CANONICAL`, +`E_LIMIT`, `E_SHAPE`, `E_REF`, and `E_RELATION`. `E_RUNTIME` covers a missing or +wrong jq runtime and internal failure; candidate data cannot turn it into a valid +result. The wrapper never prints document content or local paths in an error. + +The implementation files are: + +- `core/v1/contracts.jq` — the sole executable contract source, at most 220 net lines; +- `scripts/core-contract.sh` — byte limits, canonical comparison, hashing, and the + four safe modes, at most 45 net lines; +- `scripts/test/portable-core-contract.test.sh` — table-driven positive and + adversarial tests with generated mutations, at most 100 net lines; +- small canonical fixtures only when table generation cannot express a byte case; +- README, `ci/required-files.txt`, and operator-applied CI wiring, together at most + 35 net lines. A fixture line consumes the same 400-line total budget. + +The line budget depends on one declarative field/capability registry consumed by +generic exact-object, tagged-union, set, and relation helpers. Tests build one valid +seven-document bundle and apply table-driven mutations; they do not duplicate one +fixture per rule. If the plan needs per-capability validators or repeated fixture +trees, the estimate has failed and the work returns to G2 before code. + +Implementation order is canonical parser and shared shapes; document shapes and +registry; relational modes and status/evidence rules; then adversarial tests, docs, +restore manifest, and CI. No second parser or copied registry is allowed. + +### Required adversarial coverage + +Tests reject malformed/noncanonical/oversized JSON; BOM, duplicate/escaped keys, +deep/wide/long data, floats and integer limits; unknown fields/versions/kinds; +invalid extension keys/values or an extension used in place of a required core +field; unsafe IDs, refs, paths, +hashes, modes, source provenance, or floating refs; capability wildcards, role +mismatch, extra/missing arguments, command/argv/env/URL/network/secret/entrypoint +fields, permission drift, and model-role drift; shared protected-role bindings; +request/profile/result mismatch, including a changed request body with the same ID; +wrong performer/package/config/environment or independent-review evidence from a +non-reviewer; altered risk/selection/qualification/grant/gate refs; unexecuted +evidence or output; invalid status/outcome/evidence/time rules; replayed proof; +missing resolved bindings, wrong outcome family, free/unbound stale selectors, +mixed evidence precedence errors, and unoffered execution tools; +changed delta with replayed evidence, performer authority/version drift, missing +or wrong-purpose publisher-policy refs; invalid execution availability shapes, +source-claim refs, and model/deterministic combinations; +the absent generic label capability; empty or replaced inventories; +expected fields in results; dropped/extra/duplicate cases or assertions; mismatched +acceptance scope; and pass-looking prose, silence, empty, or degraded records that +omit required structured fields. Core tests do not judge whether a well-shaped +actual-fact or extension claim is truthful. + +Positive cases cover all seven kinds and all thirteen capabilities, including one +adapter implementation used through separate protected bindings. Core tests use +neutral logical IDs and do not special-case ystack. The unrelated Git target, +physical object attacks, fake processes, 2×2 substitution, timeouts, cleanup, and +external-target smoke belong to the two sibling initiatives. +Protected-context lookup and directive-bearing text bytes are required later +publisher/control-foundation tests, not observable core-validator cases. + +### Intent questions resolved + +1. **Smallest top-level set:** seven kinds. Five describe normal stage/profile + traffic. Separate inventory and observation records keep expectations outside the + runner result without creating a second contract family. Evidence and authority + records remain nested or referenced. +2. **Capabilities and permissions:** the thirteen-row closed registry and fifteen + permissions above are v1. Every argument field is named and typed. Anything that + cannot be expressed without a generic command, network request, credential, or + unbounded write is absent and requires a later major version. +3. **Validator boundary:** core checks canonical bytes, limits, shape, lexical refs, + closed registries, offer/request relations, protected-role separation, + request/result/evidence/status/time relations, and inventory/result linkage. It + does not read Git, authenticate, execute, authorize, or publish. +4. **Profile-resolution seam:** the resolver consumes canonical profile/manifests, + exact caller-supplied source and repository context, plus selection refs and + physical repository mappings. It produces canonical `resolved_profile` with + derived source-value claims. The sibling owns Git algorithms, object/mode/symlink + checks, replacement-object disabling, provenance derivation, and physical-path + safety. +5. **Adapter-test seam:** core owns the inventory/result envelopes and pure linkage + mode. The sibling owns external inventory selection, the fake-only launcher, + process and credential clearing, independent execution/revalidation, resolver + calls for Git facts, 2×2 comparison, unrelated-target fixtures, and proof that + observations came from execution rather than copied expectations. + +## Out of scope + +- Git reads, repository-ID-to-path mappings, profile resolution, object existence, + file mode or symlink checks, replacement-object handling, and physical containment. +- Executable manifests, fake or real adapter launch, raw fixture reads, process + protocol, timeouts, environment/credential clearing, 2×2 execution, and the + unrelated-target smoke. +- Authentication, credentials, secrets, permission or qualification issuance, risk + policy evaluation, gate decisions, runtime sandbox/network enforcement, and + publisher execution, generic label projection, or any actual external write. +- Real/default/alternative adapter extraction, Codex native review, neutral-manager + implementation, profile activation, migration, packaging, install, or upgrade. +- Durable orchestration, retries, reconciliation, backpressure, kill switch, + deployment, rollback, incidents, production feedback, eval qualification, + telemetry aggregation, dashboards, or cost policy. +- Skill migration, bridge generation, YAML or Agent Skills conformance parsing, + bundled skill execution, non-Git canonical stores, or changes to artifact + frontmatter. +- Continuing PR #154 or implementation under `portable-control-plane-core`, closing + the parent roadmap item, or claiming that valid records prove portability, + authorization, isolation, or correct execution. + +## Areas of concern + +1. **The old spec is not authority.** PR #154 closed unmerged and superseded. This + child restates only the record decisions needed now; it does not inherit the old + resolver or runner design. +2. **Pure validation has a hard honesty boundary.** A Git/content/actor/evidence ref + remains a claim. Downstream work must not advertise core exit 0 as proof of + existence, identity, authorization, or execution. +3. **Closed arguments are a G2 blocker.** If implementation needs an argument not + listed here, it returns to the artifact gate. It cannot add an opaque object, + command field, or namespaced execution escape. +4. **The runner cannot certify itself.** Inventory/result linkage prevents dropped + expectations but not fabricated observations. The sibling must independently run + and revalidate every case before a stage result can carry evidence. +5. **Declarative separation is not isolation.** Distinct IDs and refs do not create + separate credentials, sandboxes, or processes. Control-foundation work must prove + those runtime boundaries. +6. **Publisher records are proposals only.** Comment, status, branch, and + change-request shapes carry no permission to write and cannot project themselves + into gate authority. +7. **Implementation size may still expose excess scope.** The old 800-line exception + is gone. If the plan cannot stay within the normal review budget using one schema + and table-driven tests, reduce v1 or split again rather than weakening checks. +8. **CI is a constitution path.** The implementation plan must identify an + operator-driven edit or a `proposals/` handoff. G2 merge alone authorizes neither. +9. **No exceptional implementation is accepted here.** If jq limits, duplicate-key + handling, or portability require an architectural exception, return to the + accepted-artifact gate before code and satisfy the exceptional implementation + rule. Do not hide it in a parser workaround. +10. **The north-star marker is intentional.** The ystack-self entry keeps its + shipped-default marker and operator-history note for adopters. This user-directed + G2 adds no new proactive authorization and does not approve a live profile change. +11. **This is high-risk architecture.** G2 accepts design only. A later plan must be + reviewed under the repo's then-live risk gate; nobody may claim a pre-code plan + gate passed merely because this spec merged. +12. **Nothing activates on merge.** Contract/source changes do not regenerate + `/yshifu`, replace the manager persona, change adapters, or update an open + session. The operator remains the only merge authority. From 7f33e381827a32f73c95eb808ef5a03f1abc4306 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 16:00:01 -0400 Subject: [PATCH 02/15] spec: close parser and snapshot gaps --- work/portable-core-contracts/spec.md | 34 ++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index ba1853d..571ed76 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -24,8 +24,9 @@ authority, or changes the live ystack profile. selection, qualification, grant, gate, and inventory-acceptance records stay outside the core and enter only through typed immutable references. - **R3 — strict canonical JSON.** Every document is UTF-8 JSON whose complete bytes - equal pinned jq 1.6 invoked as `jq -S -c .`, followed by one line feed. Reject a - BOM, invalid UTF-8, + equal the pinned jq 1.6 single-root canonicalizer defined in Design, followed by + one line feed. Reject a BOM, invalid UTF-8, an empty stream, more than one root + JSON value, duplicate keys, alternate escaping or whitespace, floats, negative integers, `null`, unknown fields, and non-canonical bytes. The full canonical envelope is what a document digest covers. @@ -153,11 +154,13 @@ no leading or trailing hyphen, then `/`, then a 1–128 character lowercase leaf from letters, digits, `.`, `_`, or `-`. For example, `com.example/trace` is valid; `trace`, `Com.example/x`, and `com..example/x` are not. -The wrapper checks the byte limit before parsing. It canonicalizes once with the -pinned jq, compares the emitted bytes with the original, then applies the depth, -member, string, integer, shape, and relational checks. The byte comparison makes a -duplicate key or alternate escape non-canonical even though jq would otherwise keep -only the final decoded key. Regression tests protect that boundary. +The wrapper checks the byte limit before parsing. It invokes pinned jq 1.6 as +`jq -s -S -c 'if length == 1 then .[0] else error("root-count") end'`, which rejects +empty and multi-value streams before selecting the one root and emitting canonical +bytes. It compares those bytes with the original, then applies depth, member, +string, integer, shape, and relational checks. The byte comparison makes a duplicate +key or alternate escape non-canonical even though jq would otherwise keep only the +final decoded key. Regression tests protect that boundary. ### Normative schema notation and shared shapes @@ -186,6 +189,7 @@ segments made from lowercase letters, digits, `.`, `_`, or `-`; no segment is `. `ExtensionPrefix` is the reverse-domain portion of the extension-key grammar. `canonical-sha256` as a set key means SHA-256 of the nested value's canonical JSON; `value` means the primitive string itself. +`ErrorCode` matches `^E_[A-Z][A-Z0-9_]{0,62}$`; it is not a lowercase core `ID`. The adapter-role registry is exactly `producer`, `verifier`, `reviewer`, `forge`, `ci`, `execution`, `identity`, and `publisher`. Actor roles may also be `operator`, @@ -310,12 +314,12 @@ those bytes. - `test_assertion_result` is `{assertion_id:ID,passed:Boolean}`. - `test_case` is `{case_id:ID,phase:TestPhase,fixture_ref:artifact_ref, expected_status:"accepted"|"rejected"|"transport-failed", - expected_error_id?:ID,equivalence_group?:ID, + expected_error_code?:ErrorCode,equivalence_group?:ID, assertion_ids:set(value,1..256)}`. Error is forbidden for `accepted` and required otherwise. - `test_observation` is `{case_id:ID,phase:TestPhase, observed_status:"accepted"|"rejected"|"transport-failed"|"inconclusive", - observed_error_id?:ID,produced_document_refs:set(sha256,0..256), + observed_error_code?:ErrorCode,produced_document_refs:set(sha256,0..256), assertions:set(assertion_id,1..256)}`. Error is forbidden for accepted and required otherwise. @@ -480,7 +484,7 @@ credential reads. | reviewer / `core.review.advise.v1` | `{change_ref:change_ref,review_policy_ref:scope_ref(review-policy)}` | `core.perm.target.read.v1`, `core.perm.evidence.write.v1` | advisory / `{R}`; R | | forge / `core.forge.observe.v1` | `{observation_kind:ForgeObservation,subject:ForgeSubject}` | `core.perm.forge.read.v1` | check / `{D}`; D | | ci / `core.ci.observe.v1` | `{repository_id:ID,commit_ref:git_revision_ref,check_set_ref:scope_ref(check-set),required_only:true}` | `core.perm.ci.read.v1` | check / `{D}`; D | -| execution / `core.execution.provision.v1` | `{environment_spec_ref:scope_ref(environment-policy),input_snapshot_ref:artifact_ref,network_mode:"deny",tool_refs:set(tool_id,0..256)}` | `core.perm.execution.provision.v1` | check / `{D}`; D | +| execution / `core.execution.provision.v1` | `{environment_spec_ref:scope_ref(environment-policy),input_snapshot_ref:git_object_ref,network_mode:"deny",tool_refs:set(tool_id,0..256)}` | `core.perm.execution.provision.v1` | check / `{D}`; D | | identity / `core.identity.resolve.v1` | `{subject_actor_ref:actor_ref,purpose:"performer"|"verifier"|"reviewer"|"publisher"|"observer"}` | `core.perm.identity.read.v1` | check / `{D}`; D | | publisher / `core.publish.branch-bounded.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),topic_ref:TopicRef,expected_old_tip:present,new_commit_ref:git_revision_ref,delta_ref:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.topic-ref.write.v1` | change / `{D}`; D | | publisher / `core.publish.change-request.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),source_result_ref:document_ref(stage_result),head_ref:{name:TopicRef,commit:git_revision_ref},base_ref:git_revision_ref,title_ref:content_ref,body_ref:content_ref,draft:Boolean,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.change-request.write.v1` | change / `{D}`; D | @@ -582,7 +586,7 @@ can never stand in for a gate decision. `TestPhase` is exactly `parse`, `document`, `profile-set`, `stage-run`, `adapter-run`, or `matrix`. Expected observation is `accepted`, `rejected`, or -`transport-failed`, with an exact stable error ID when applicable. The inventory +`transport-failed`, with an exact stable `ErrorCode` when applicable. The inventory case set and each assertion-ID set are non-empty, sorted, and unique. Each result observation has case ID, phase, observed status/error, produced document @@ -653,10 +657,11 @@ restore manifest, and CI. No second parser or copied registry is allowed. ### Required adversarial coverage -Tests reject malformed/noncanonical/oversized JSON; BOM, duplicate/escaped keys, +Tests reject malformed/noncanonical/oversized JSON; empty or multiple-root streams; +BOM, duplicate/escaped keys, deep/wide/long data, floats and integer limits; unknown fields/versions/kinds; -invalid extension keys/values or an extension used in place of a required core -field; unsafe IDs, refs, paths, +invalid error-code or extension keys/values, or an extension used in place of a +required core field; unsafe IDs, refs, paths, hashes, modes, source provenance, or floating refs; capability wildcards, role mismatch, extra/missing arguments, command/argv/env/URL/network/secret/entrypoint fields, permission drift, and model-role drift; shared protected-role bindings; @@ -666,6 +671,7 @@ non-reviewer; altered risk/selection/qualification/grant/gate refs; unexecuted evidence or output; invalid status/outcome/evidence/time rules; replayed proof; missing resolved bindings, wrong outcome family, free/unbound stale selectors, mixed evidence precedence errors, and unoffered execution tools; +content-backed or wrong-repository execution snapshots; changed delta with replayed evidence, performer authority/version drift, missing or wrong-purpose publisher-policy refs; invalid execution availability shapes, source-claim refs, and model/deterministic combinations; From cda79446b45d0965119284d8224c835d4441185e Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 16:13:47 -0400 Subject: [PATCH 03/15] spec: bind change base and tool config --- work/portable-core-contracts/spec.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 571ed76..4dd8fd6 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -212,7 +212,7 @@ An `operator` actor ref is attribution only and never a human decision record. | `scope_ref(P?)` | `{purpose:ScopePurpose,decision_record_ref:content_ref,subject_ref:scope_subject_ref,scope_sha256:SHA256}`; when `P` is supplied, `purpose=P` | | `actor_ref` | `{role:ActorRole,implementation_id:ID,implementation_version:Version,adapter_instance_id:ID,principal_id:ID,execution_boundary_id:ID,authority_ref?:scope_ref(authority)}` | | `environment_ref` | `{environment_id:ID,fingerprint_sha256:SHA256}` | -| `tool_ref` | `{tool_id:ID,tool_version:Version,package_ref:artifact_ref,config_sha256:SHA256}` | +| `tool_ref` | `{tool_id:ID,tool_version:Version,package_ref:artifact_ref,config_ref:present}` | | `change_ref` | `{repository_id:ID,base:git_revision_ref,head:git_revision_ref,delta_ref:content_ref}`; both revisions use `repository_id` | `ScopePurpose` is exactly `selection`, `repository-context`, `qualification`, @@ -245,7 +245,8 @@ those bytes. requested_capabilities:set(value,1..256), requested_permissions:set(value,1..256)}`. `model_request` and `prompt_ref` are required exactly when `execution_kind=model`. Tool refs inside a - manifest or profile require a `git-object` package ref. + manifest or profile require a `git-object` package ref and, when config is + present, a `git-object` config ref. - `resolved_binding` is exactly `{binding:profile_binding, adapter_implementation:{id:ID,version:Version}, manifest_source:source_value_ref,package_source:source_value_ref, @@ -487,7 +488,7 @@ credential reads. | execution / `core.execution.provision.v1` | `{environment_spec_ref:scope_ref(environment-policy),input_snapshot_ref:git_object_ref,network_mode:"deny",tool_refs:set(tool_id,0..256)}` | `core.perm.execution.provision.v1` | check / `{D}`; D | | identity / `core.identity.resolve.v1` | `{subject_actor_ref:actor_ref,purpose:"performer"|"verifier"|"reviewer"|"publisher"|"observer"}` | `core.perm.identity.read.v1` | check / `{D}`; D | | publisher / `core.publish.branch-bounded.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),topic_ref:TopicRef,expected_old_tip:present,new_commit_ref:git_revision_ref,delta_ref:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.topic-ref.write.v1` | change / `{D}`; D | -| publisher / `core.publish.change-request.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),source_result_ref:document_ref(stage_result),head_ref:{name:TopicRef,commit:git_revision_ref},base_ref:git_revision_ref,title_ref:content_ref,body_ref:content_ref,draft:Boolean,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.change-request.write.v1` | change / `{D}`; D | +| publisher / `core.publish.change-request.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),source_result_ref:document_ref(stage_result),head_ref:{name:TopicRef,commit:git_revision_ref},base_ref:{name:TopicRef,commit:git_revision_ref},title_ref:content_ref,body_ref:content_ref,draft:Boolean,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.change-request.write.v1` | change / `{D}`; D | | publisher / `core.publish.comment.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),subject:{kind:"issue"|"change-request",id:ID},source_result_ref:document_ref(stage_result),body_ref:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.comment.write.v1` | change / `{D}`; D | | publisher / `core.publish.status.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),commit_ref:git_revision_ref,source_result_ref:document_ref(stage_result),context_id:ID,state:"success"|"failure"|"neutral",details_ref?:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.status.write.v1` | change / `{D}`; D | @@ -522,7 +523,8 @@ are an allowed output of that result. later publisher verifies real branch policy, current tip, ancestry, actual delta, credential scope, and atomic compare-and-swap. Branch arguments cannot express force, delete, merge, or bypass. Change-request arguments can only ensure/open a -request; they cannot close, merge, approve, or assign a reviewer. Status is a +request; the named head and base refs must still advertise their supplied commits +at the final read. They cannot close, merge, approve, or assign a reviewer. Status is a projection, not a gate result. A generic label write is deliberately absent because labels may carry gate state; a later fixed projection needs a new core version. @@ -562,6 +564,7 @@ resolved binding.binding equals the profile binding resolved adapter implementation ID/version equal manifest envelope ID/body version profile/manifest source-value canonical-json digests equal their document refs package/config/prompt/skill/tool source presence and object refs equal the binding refs +resolved tool-source object-ref set equals the distinct requested tool package/config refs request resolved-profile ref equals the recomputed supplied resolved-profile ref request selection/repository-context refs equal supplied resolved-profile body refs operation binding ID and role equal the selected resolved binding @@ -672,6 +675,7 @@ evidence or output; invalid status/outcome/evidence/time rules; replayed proof; missing resolved bindings, wrong outcome family, free/unbound stale selectors, mixed evidence precedence errors, and unoffered execution tools; content-backed or wrong-repository execution snapshots; +unnamed or moved change-request base refs and tool configs without immutable refs; changed delta with replayed evidence, performer authority/version drift, missing or wrong-purpose publisher-policy refs; invalid execution availability shapes, source-claim refs, and model/deterministic combinations; From 5f3f9cd9453166d14d8638d800a95634bfed8cac Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 16:27:58 -0400 Subject: [PATCH 04/15] spec: make provenance unique and bounded --- work/portable-core-contracts/spec.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 4dd8fd6..ea4e881 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -188,6 +188,7 @@ segments made from lowercase letters, digits, `.`, `_`, or `-`; no segment is `. `..`, ends in `.` or `.lock`, or contains `..`. `IdempotencyKey` is an `ID`. `ExtensionPrefix` is the reverse-domain portion of the extension-key grammar. `canonical-sha256` as a set key means SHA-256 of the nested value's canonical JSON; +`source-canonical-sha256` means SHA-256 of only a `source_value_ref.source` object; `value` means the primitive string itself. `ErrorCode` matches `^E_[A-Z][A-Z0-9_]{0,62}$`; it is not a lowercase core `ID`. @@ -241,7 +242,7 @@ those bytes. authority_ref?:scope_ref(authority),package_ref:git_object_ref, config_ref?:git_object_ref,prompt_ref?:git_object_ref, skill_refs:set(canonical-sha256,0..256), - requested_tool_refs:set(tool_id,0..256),model_request?:model_request, + requested_tool_refs:set(tool_id,0..128),model_request?:model_request, requested_capabilities:set(value,1..256), requested_permissions:set(value,1..256)}`. `model_request` and `prompt_ref` are required exactly when `execution_kind=model`. Tool refs inside a @@ -251,9 +252,12 @@ those bytes. adapter_implementation:{id:ID,version:Version}, manifest_source:source_value_ref,package_source:source_value_ref, config_source?:source_value_ref,prompt_source?:source_value_ref, - skill_sources:set(canonical-sha256,0..256), - tool_sources:set(canonical-sha256,0..256)}`. Optional/source set + skill_sources:set(source-canonical-sha256,0..256), + tool_sources:set(source-canonical-sha256,0..256)}`. Optional/source set presence and source object refs exactly match the binding refs they claim to resolve. + A source object appears at most once in each source set. If the same source object + appears in another resolved field, its value format and digest are identical. + At most 128 requested tools need at most 256 distinct package/config sources. - `named_input` is `{input_id:ID,value:input_value_ref}`. - `risk_claim` is `{tier:{namespace:"core",name:"routine"|"high"|"bootstrap"} |{namespace:ExtensionPrefix,name:ID},reason_ids:set(value,1..256), @@ -284,7 +288,7 @@ those bytes. provider:availability,model:availability,snapshot:availability, effort:availability,prompt:availability, skills:availability(canonical-sha256,0..256)>, - tools:availability(tool_id,0..256)>,trace:availability, + tools:availability(tool_id,0..128)>,trace:availability, usage:availability,cost:availability}`. Deterministic execution requires provider/model/snapshot/effort/prompt/skills to be `not-applicable`; model execution requires each of those fields to be recorded, @@ -336,7 +340,7 @@ do not repeat it. offered_execution_kinds:set<"model"|"deterministic">(value,1..2), offered_capabilities:set(value,1..256), offered_permissions:set(value,1..256), - offered_tool_refs:set(tool_id,0..256), + offered_tool_refs:set(tool_id,0..128), config_contract_ref?:scope_ref(config-contract)}`. - `profile.body` is `{profile_version:Version, bindings:set(binding_id,1..8)}` with at most one binding per @@ -485,7 +489,7 @@ credential reads. | reviewer / `core.review.advise.v1` | `{change_ref:change_ref,review_policy_ref:scope_ref(review-policy)}` | `core.perm.target.read.v1`, `core.perm.evidence.write.v1` | advisory / `{R}`; R | | forge / `core.forge.observe.v1` | `{observation_kind:ForgeObservation,subject:ForgeSubject}` | `core.perm.forge.read.v1` | check / `{D}`; D | | ci / `core.ci.observe.v1` | `{repository_id:ID,commit_ref:git_revision_ref,check_set_ref:scope_ref(check-set),required_only:true}` | `core.perm.ci.read.v1` | check / `{D}`; D | -| execution / `core.execution.provision.v1` | `{environment_spec_ref:scope_ref(environment-policy),input_snapshot_ref:git_object_ref,network_mode:"deny",tool_refs:set(tool_id,0..256)}` | `core.perm.execution.provision.v1` | check / `{D}`; D | +| execution / `core.execution.provision.v1` | `{environment_spec_ref:scope_ref(environment-policy),input_snapshot_ref:git_object_ref,network_mode:"deny",tool_refs:set(tool_id,0..128)}` | `core.perm.execution.provision.v1` | check / `{D}`; D | | identity / `core.identity.resolve.v1` | `{subject_actor_ref:actor_ref,purpose:"performer"|"verifier"|"reviewer"|"publisher"|"observer"}` | `core.perm.identity.read.v1` | check / `{D}`; D | | publisher / `core.publish.branch-bounded.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),topic_ref:TopicRef,expected_old_tip:present,new_commit_ref:git_revision_ref,delta_ref:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.topic-ref.write.v1` | change / `{D}`; D | | publisher / `core.publish.change-request.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),source_result_ref:document_ref(stage_result),head_ref:{name:TopicRef,commit:git_revision_ref},base_ref:{name:TopicRef,commit:git_revision_ref},title_ref:content_ref,body_ref:content_ref,draft:Boolean,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.change-request.write.v1` | change / `{D}`; D | @@ -565,6 +569,7 @@ resolved adapter implementation ID/version equal manifest envelope ID/body versi profile/manifest source-value canonical-json digests equal their document refs package/config/prompt/skill/tool source presence and object refs equal the binding refs resolved tool-source object-ref set equals the distinct requested tool package/config refs +repeated source objects anywhere in the resolved profile have identical format/digest request resolved-profile ref equals the recomputed supplied resolved-profile ref request selection/repository-context refs equal supplied resolved-profile body refs operation binding ID and role equal the selected resolved binding @@ -676,6 +681,7 @@ missing resolved bindings, wrong outcome family, free/unbound stale selectors, mixed evidence precedence errors, and unoffered execution tools; content-backed or wrong-repository execution snapshots; unnamed or moved change-request base refs and tool configs without immutable refs; +duplicate source objects with conflicting provenance and a 129-tool binding; changed delta with replayed evidence, performer authority/version drift, missing or wrong-purpose publisher-policy refs; invalid execution availability shapes, source-claim refs, and model/deterministic combinations; From 10659fd23837fdd32825c43c3c36c2fd0f867c06 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 17:28:54 -0400 Subject: [PATCH 05/15] spec: preserve observed execution mismatches --- work/portable-core-contracts/spec.md | 117 ++++++++++++++++++--------- 1 file changed, 81 insertions(+), 36 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index ea4e881..cc7597a 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -62,22 +62,25 @@ authority, or changes the live ystack profile. - **R8 — total result rules.** A stage result binds one exact request and one attempt. It records the controller, terminal status, whether the operation ran, the outcome when allowed, reason and diagnostics, expected/observed identities, - outputs, evidence, actual performer and capability when executed, actual - execution facts, and ordered times. Every status/outcome combination is defined + outputs, evidence, actual performer, binding, environment, and capability when + executed, actual execution facts, and ordered times. Every status/outcome combination is defined in Design; all others fail. `completed` means the operation finished, not that it passed. - **R9 — actual execution facts stay separate from requested config.** An executed - result records its actual adapter/package/config, environment, tools, and either + result records its observed adapter/package/config, environment, tools, and either deterministic or model execution metadata. Every possibly hidden actual model fact—provider, model, snapshot, effort, prompt, skills, tools, trace, usage, and cost—uses the same typed availability union. Missing facts are `unavailable` with - a reason, never guessed or copied from the profile. An unexecuted result + a reason, never guessed or copied from the profile. Failed, cancelled, or + completed-inconclusive execution preserves any observed performer, binding, or + environment mismatch instead of rewriting it to the request. An unexecuted result cannot carry a performer, used capability, execution metadata, evidence, or a successful output. - **R10 — evidence is exact and non-transferable.** Each evidence record carries the complete `stage_request` document ref, attempt ID/number, finish condition, every enclosing output ref, resolved profile, qualification scope when present, - environment, performer, actual binding/package/config, verification instructions, + observed environment, performer, actual binding/package/config, exact mismatch + set, verification instructions, proof bytes, kind, and verdict. A completed result covers exactly the evidence kinds requested before execution. Evidence from another request body, attempt, output, profile, environment, or instruction version cannot be replayed as current @@ -282,6 +285,12 @@ those bytes. package_ref:git_object_ref,config_ref?:git_object_ref,execution_kind:"model"| "deterministic",adapter_instance_id:ID,principal_id:ID, execution_boundary_id:ID,authority_ref?:scope_ref(authority)}`. +- `execution_mismatch` is exactly + `{field:"performer",expected:actor_ref,observed:actor_ref}`, + `{field:"binding",expected:actual_binding,observed:actual_binding}`, or + `{field:"environment",expected:environment_ref,observed:environment_ref}`. + Expected is derived from the request/resolved binding, observed equals the result, + and the two canonical values differ. - `usage_value` is `{input_tokens:Int,output_tokens:Int,cache_read_tokens:Int, cache_write_tokens:Int}`. `cost_value` is `{currency_id:ID,microunits:Int}`. - `execution_metadata` is exactly `{kind:"model"|"deterministic", @@ -299,6 +308,7 @@ those bytes. resolved_profile_ref:document_ref(resolved_profile), qualification_ref?:scope_ref(qualification),environment_ref:environment_ref, performer:actor_ref,actual_binding:actual_binding, + execution_mismatches:set(field,0..3), verification_instruction_ref:scope_ref(verification-instructions), outputs:set(output_id,0..256),delta_ref?:content_ref, kind:EvidenceKind, @@ -373,6 +383,8 @@ do not repeat it. outputs:set(output_id,0..256),delta_ref?:content_ref, diagnostics:set(content_id,0..256),performer?:actor_ref, used_capability?:CapabilityID,actual_binding?:actual_binding, + actual_environment_ref?:environment_ref, + execution_mismatches?:set(field,0..3), execution_metadata?:execution_metadata, evidence:set(evidence_id,0..256),started_at?:Time, finished_at?:Time,recorded_at:Time}`. Presence follows the total status table. @@ -413,8 +425,9 @@ An advisory is data for a later gate. It is never approval or a gate decision. | `cancelled` | true | only an inconclusive outcome; reason and attempt evidence required; no successful subject output or delta | Outcome is present exactly when `executed=true`. Performer, used capability, -actual binding, execution metadata, start time, and finish time are also present -exactly when executed. Reason is required for every non-completed status and every +actual binding, actual environment, execution-mismatch set, execution metadata, +start time, and finish time are also present exactly when executed. Reason is +required for every non-completed status and every inconclusive outcome; it is forbidden for a completed non-inconclusive outcome. `stale_comparisons` is present exactly for `stale`; every expected value is derived from its request selector, every observed value is type-compatible, and every pair @@ -451,20 +464,37 @@ subset of the request's required kinds, every verdict is `failed` or `inconclusive`, at least one is non-passing, and its output set is empty. It records only the interrupted attempt and cannot satisfy a later completed result. An unexecuted result has an empty evidence set. +An exact performer mismatch on a reviewer request has one narrow incident rule. If +the observed performer is not a reviewer, the failed, cancelled, or +completed-inconclusive attempt may still carry the requested `independent-review` +kind with only `failed|inconclusive` verdicts and the observed performer. This +records that independent review did not occur; it is never passing review evidence, +cannot satisfy a completed non-inconclusive result, and cannot be reused to satisfy +a later attempt. + `validate-stage-run` recomputes the complete request document ref and requires it to equal the result and every nested evidence `request_ref`. Evidence attempt values equal the enclosing result. Finish condition, profile, qualification presence, -environment, verification instructions, performer, actual binding, every output -record, and delta presence/value also equal the request/result/binding values exactly. Every evidence -record on a completed result covers the complete output set. Prior-stage evidence +verification instructions, every output record, and delta presence/value equal the +request/result values exactly. Every evidence record on a completed result covers +the complete output set. Prior-stage evidence appears only in `request.prior_evidence_refs`; its body is never copied into the new result. -For an executed result, `performer` matches the selected resolved binding's role, -implementation, instance, principal, and boundary. `actual_binding` matches that -binding's manifest/package/config and execution kind. Execution metadata kind -matches it too. All nested evidence repeats those exact performer, binding, and -environment values. These are equality checks over claims, not identity proof. +For every executed result, the validator derives the expected performer and +`actual_binding` projection from the selected resolved binding, and takes the +expected environment from the request. A completed non-inconclusive result requires +all three observed values to equal those expectations and has an empty mismatch +set. An executed `failed` or `cancelled` result, or a completed-inconclusive result, +may differ. Its mismatch set contains exactly one record for each differing field, +no equal or missing field, with expected derived from the request/binding and +observed equal to the result's actual value. The set may be empty when failure or +inconclusive outcome has another cause. + +Execution metadata kind always matches the observed actual binding. Every nested +evidence record repeats the result's observed performer, actual binding, actual +environment, and complete mismatch set. The request ref in that evidence preserves +the expected values. These are equality checks over claims, not identity proof. Every possibly hidden model fact uses `availability` as defined above. A profile model request is desired configuration only and cannot fill an actual result field. @@ -549,8 +579,9 @@ Only producer and reviewer bindings may use `execution_kind: model`. Their effec permission set adds exactly `core.perm.model.invoke.v1`; every other role is deterministic. Request evidence kinds are a subset of the capability's closed allowed set and include every required kind. Only either review capability may -produce `independent-review` evidence. For every supplied profile set and stage run, -validation enforces: +produce passing `independent-review` evidence. An observed non-reviewer may carry +that kind only under the exact performer-mismatch incident rule above. For every +supplied profile set and stage run, validation enforces: ```text supplied manifest document refs equal the profile binding manifest-ref set @@ -579,11 +610,14 @@ operation permissions equal that capability's effective permissions result request/resolved-profile refs equal the supplied request/profile documents executed result used capability equals the request capability executed result outcome family equals the request capability registry family -executed result metadata kind equals the resolved binding execution kind -recorded/computed actual tools are a subset of resolved binding requested tools -executed result performer and actual binding equal the resolved binding -performer implementation ID/version and authority presence/value equal that binding -all evidence performer/binding/environment values equal the result and request +expected performer/binding/environment derive from resolved binding and request +completed non-inconclusive actual values equal expected and mismatch set is empty +failed/cancelled/completed-inconclusive mismatch set exactly covers actual differences +executed result metadata kind equals observed actual-binding execution kind +completed non-inconclusive actual tools are a subset of resolved-binding + requested tools +all evidence performer/binding/environment/mismatch values equal observed result facts +non-reviewer R-kind evidence is non-passing and requires the exact incident performer mismatch ``` The core validates only the shape of a publisher proposal and these document @@ -674,15 +708,19 @@ hashes, modes, source provenance, or floating refs; capability wildcards, role mismatch, extra/missing arguments, command/argv/env/URL/network/secret/entrypoint fields, permission drift, and model-role drift; shared protected-role bindings; request/profile/result mismatch, including a changed request body with the same ID; -wrong performer/package/config/environment or independent-review evidence from a -non-reviewer; altered risk/selection/qualification/grant/gate refs; unexecuted +an actual performer/binding/environment mismatch on a completed non-inconclusive +result; missing, extra, equal, or incorrectly derived mismatch records; evidence +that does not bind observed actual facts; passed independent-review evidence from a +non-reviewer, or non-reviewer R-kind evidence without the exact incident mismatch; +altered risk/selection/qualification/grant/gate refs; unexecuted evidence or output; invalid status/outcome/evidence/time rules; replayed proof; missing resolved bindings, wrong outcome family, free/unbound stale selectors, mixed evidence precedence errors, and unoffered execution tools; content-backed or wrong-repository execution snapshots; unnamed or moved change-request base refs and tool configs without immutable refs; duplicate source objects with conflicting provenance and a 129-tool binding; -changed delta with replayed evidence, performer authority/version drift, missing +changed delta with replayed evidence, unrecorded performer authority/version drift, +missing or wrong-purpose publisher-policy refs; invalid execution availability shapes, source-claim refs, and model/deterministic combinations; the absent generic label capability; empty or replaced inventories; @@ -692,7 +730,10 @@ omit required structured fields. Core tests do not judge whether a well-shaped actual-fact or extension claim is truthful. Positive cases cover all seven kinds and all thirteen capabilities, including one -adapter implementation used through separate protected bindings. Core tests use +adapter implementation used through separate protected bindings, plus executed +failed, cancelled, and completed-inconclusive results that preserve each allowed +actual mismatch, including a reviewer performer-mismatch incident with non-passing +R-kind evidence. Core tests use neutral logical IDs and do not special-case ystack. The unrelated Git target, physical object attacks, fake processes, 2×2 substitution, timeouts, cleanup, and external-target smoke belong to the two sibling initiatives. @@ -755,33 +796,37 @@ publisher/control-foundation tests, not observable core-validator cases. 2. **Pure validation has a hard honesty boundary.** A Git/content/actor/evidence ref remains a claim. Downstream work must not advertise core exit 0 as proof of existence, identity, authorization, or execution. -3. **Closed arguments are a G2 blocker.** If implementation needs an argument not +3. **Observed mismatch is incident data, not authority.** A valid failed or + inconclusive result may preserve the wrong actual actor/binding/environment. That + never authorizes it; control-foundation and orchestration work must stop the + workflow and route the incident to the correct recovery gate. +4. **Closed arguments are a G2 blocker.** If implementation needs an argument not listed here, it returns to the artifact gate. It cannot add an opaque object, command field, or namespaced execution escape. -4. **The runner cannot certify itself.** Inventory/result linkage prevents dropped +5. **The runner cannot certify itself.** Inventory/result linkage prevents dropped expectations but not fabricated observations. The sibling must independently run and revalidate every case before a stage result can carry evidence. -5. **Declarative separation is not isolation.** Distinct IDs and refs do not create +6. **Declarative separation is not isolation.** Distinct IDs and refs do not create separate credentials, sandboxes, or processes. Control-foundation work must prove those runtime boundaries. -6. **Publisher records are proposals only.** Comment, status, branch, and +7. **Publisher records are proposals only.** Comment, status, branch, and change-request shapes carry no permission to write and cannot project themselves into gate authority. -7. **Implementation size may still expose excess scope.** The old 800-line exception +8. **Implementation size may still expose excess scope.** The old 800-line exception is gone. If the plan cannot stay within the normal review budget using one schema and table-driven tests, reduce v1 or split again rather than weakening checks. -8. **CI is a constitution path.** The implementation plan must identify an +9. **CI is a constitution path.** The implementation plan must identify an operator-driven edit or a `proposals/` handoff. G2 merge alone authorizes neither. -9. **No exceptional implementation is accepted here.** If jq limits, duplicate-key +10. **No exceptional implementation is accepted here.** If jq limits, duplicate-key handling, or portability require an architectural exception, return to the accepted-artifact gate before code and satisfy the exceptional implementation rule. Do not hide it in a parser workaround. -10. **The north-star marker is intentional.** The ystack-self entry keeps its +11. **The north-star marker is intentional.** The ystack-self entry keeps its shipped-default marker and operator-history note for adopters. This user-directed G2 adds no new proactive authorization and does not approve a live profile change. -11. **This is high-risk architecture.** G2 accepts design only. A later plan must be +12. **This is high-risk architecture.** G2 accepts design only. A later plan must be reviewed under the repo's then-live risk gate; nobody may claim a pre-code plan gate passed merely because this spec merged. -12. **Nothing activates on merge.** Contract/source changes do not regenerate +13. **Nothing activates on merge.** Contract/source changes do not regenerate `/yshifu`, replace the manager persona, change adapters, or update an open session. The operator remains the only merge authority. From e27c6173b5c5d92de9936353e23dd014606b6522 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 17:46:12 -0400 Subject: [PATCH 06/15] spec: record capability mismatch and valid refs --- work/portable-core-contracts/spec.md | 45 ++++++++++++++++------------ 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index cc7597a..9a3f482 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -72,15 +72,16 @@ authority, or changes the live ystack profile. fact—provider, model, snapshot, effort, prompt, skills, tools, trace, usage, and cost—uses the same typed availability union. Missing facts are `unavailable` with a reason, never guessed or copied from the profile. Failed, cancelled, or - completed-inconclusive execution preserves any observed performer, binding, or - environment mismatch instead of rewriting it to the request. An unexecuted result + completed-inconclusive execution preserves any observed performer, binding, + environment, or capability mismatch instead of rewriting it to the request. An + unexecuted result cannot carry a performer, used capability, execution metadata, evidence, or a successful output. - **R10 — evidence is exact and non-transferable.** Each evidence record carries the complete `stage_request` document ref, attempt ID/number, finish condition, every enclosing output ref, resolved profile, qualification scope when present, - observed environment, performer, actual binding/package/config, exact mismatch - set, verification instructions, + observed environment, performer, actual binding/package/config, used capability, + exact mismatch set, verification instructions, proof bytes, kind, and verdict. A completed result covers exactly the evidence kinds requested before execution. Evidence from another request body, attempt, output, profile, environment, or instruction version cannot be replayed as current @@ -188,7 +189,8 @@ lowercase media type matching lowercase hex characters for SHA-1 or 64 for SHA-256. `RepoPath` follows the lexical rules below. `TopicRef` is `refs/heads/` plus slash-separated 1–64 character segments made from lowercase letters, digits, `.`, `_`, or `-`; no segment is `.`, -`..`, ends in `.` or `.lock`, or contains `..`. `IdempotencyKey` is an `ID`. +`..`, begins or ends with `.`, ends with `.lock`, or contains `..`. +`IdempotencyKey` is an `ID`. `ExtensionPrefix` is the reverse-domain portion of the extension-key grammar. `canonical-sha256` as a set key means SHA-256 of the nested value's canonical JSON; `source-canonical-sha256` means SHA-256 of only a `source_value_ref.source` object; @@ -288,7 +290,8 @@ those bytes. - `execution_mismatch` is exactly `{field:"performer",expected:actor_ref,observed:actor_ref}`, `{field:"binding",expected:actual_binding,observed:actual_binding}`, or - `{field:"environment",expected:environment_ref,observed:environment_ref}`. + `{field:"environment",expected:environment_ref,observed:environment_ref}`, or + `{field:"capability",expected:CapabilityID,observed:CapabilityID}`. Expected is derived from the request/resolved binding, observed equals the result, and the two canonical values differ. - `usage_value` is `{input_tokens:Int,output_tokens:Int,cache_read_tokens:Int, @@ -307,8 +310,8 @@ those bytes. finish_condition_ref:scope_ref(finish-condition), resolved_profile_ref:document_ref(resolved_profile), qualification_ref?:scope_ref(qualification),environment_ref:environment_ref, - performer:actor_ref,actual_binding:actual_binding, - execution_mismatches:set(field,0..3), + performer:actor_ref,actual_binding:actual_binding,used_capability:CapabilityID, + execution_mismatches:set(field,0..4), verification_instruction_ref:scope_ref(verification-instructions), outputs:set(output_id,0..256),delta_ref?:content_ref, kind:EvidenceKind, @@ -384,7 +387,7 @@ do not repeat it. diagnostics:set(content_id,0..256),performer?:actor_ref, used_capability?:CapabilityID,actual_binding?:actual_binding, actual_environment_ref?:environment_ref, - execution_mismatches?:set(field,0..3), + execution_mismatches?:set(field,0..4), execution_metadata?:execution_metadata, evidence:set(evidence_id,0..256),started_at?:Time, finished_at?:Time,recorded_at:Time}`. Presence follows the total status table. @@ -483,9 +486,9 @@ result. For every executed result, the validator derives the expected performer and `actual_binding` projection from the selected resolved binding, and takes the -expected environment from the request. A completed non-inconclusive result requires -all three observed values to equal those expectations and has an empty mismatch -set. An executed `failed` or `cancelled` result, or a completed-inconclusive result, +expected environment and capability from the request. A completed +non-inconclusive result requires all four observed values to equal those expectations +and has an empty mismatch set. An executed `failed` or `cancelled` result, or a completed-inconclusive result, may differ. Its mismatch set contains exactly one record for each differing field, no equal or missing field, with expected derived from the request/binding and observed equal to the result's actual value. The set may be empty when failure or @@ -493,7 +496,8 @@ inconclusive outcome has another cause. Execution metadata kind always matches the observed actual binding. Every nested evidence record repeats the result's observed performer, actual binding, actual -environment, and complete mismatch set. The request ref in that evidence preserves +environment, used capability, and complete mismatch set. The request ref in that +evidence preserves the expected values. These are equality checks over claims, not identity proof. Every possibly hidden model fact uses `availability` as defined above. A profile @@ -608,15 +612,15 @@ operation capability belongs to its resolved binding execution-provision argument tools are a subset of resolved binding requested tools operation permissions equal that capability's effective permissions result request/resolved-profile refs equal the supplied request/profile documents -executed result used capability equals the request capability executed result outcome family equals the request capability registry family -expected performer/binding/environment derive from resolved binding and request +executed result used capability records the observed actual capability +expected performer/binding/environment/capability derive from binding and request completed non-inconclusive actual values equal expected and mismatch set is empty failed/cancelled/completed-inconclusive mismatch set exactly covers actual differences executed result metadata kind equals observed actual-binding execution kind completed non-inconclusive actual tools are a subset of resolved-binding requested tools -all evidence performer/binding/environment/mismatch values equal observed result facts +all evidence performer/binding/environment/capability/mismatch values equal observed result facts non-reviewer R-kind evidence is non-passing and requires the exact incident performer mismatch ``` @@ -708,7 +712,7 @@ hashes, modes, source provenance, or floating refs; capability wildcards, role mismatch, extra/missing arguments, command/argv/env/URL/network/secret/entrypoint fields, permission drift, and model-role drift; shared protected-role bindings; request/profile/result mismatch, including a changed request body with the same ID; -an actual performer/binding/environment mismatch on a completed non-inconclusive +an actual performer/binding/environment/capability mismatch on a completed non-inconclusive result; missing, extra, equal, or incorrectly derived mismatch records; evidence that does not bind observed actual facts; passed independent-review evidence from a non-reviewer, or non-reviewer R-kind evidence without the exact incident mismatch; @@ -718,6 +722,7 @@ missing resolved bindings, wrong outcome family, free/unbound stale selectors, mixed evidence precedence errors, and unoffered execution tools; content-backed or wrong-repository execution snapshots; unnamed or moved change-request base refs and tool configs without immutable refs; +topic-ref components beginning with `.`; duplicate source objects with conflicting provenance and a 129-tool binding; changed delta with replayed evidence, unrecorded performer authority/version drift, missing @@ -732,7 +737,8 @@ actual-fact or extension claim is truthful. Positive cases cover all seven kinds and all thirteen capabilities, including one adapter implementation used through separate protected bindings, plus executed failed, cancelled, and completed-inconclusive results that preserve each allowed -actual mismatch, including a reviewer performer-mismatch incident with non-passing +actual performer/binding/environment/capability mismatch, including a reviewer +performer-mismatch incident with non-passing R-kind evidence. Core tests use neutral logical IDs and do not special-case ystack. The unrelated Git target, physical object attacks, fake processes, 2×2 substitution, timeouts, cleanup, and @@ -797,7 +803,8 @@ publisher/control-foundation tests, not observable core-validator cases. remains a claim. Downstream work must not advertise core exit 0 as proof of existence, identity, authorization, or execution. 3. **Observed mismatch is incident data, not authority.** A valid failed or - inconclusive result may preserve the wrong actual actor/binding/environment. That + inconclusive result may preserve the wrong actual actor/binding/environment or + capability. That never authorizes it; control-foundation and orchestration work must stop the workflow and route the incident to the correct recovery gate. 4. **Closed arguments are a G2 blocker.** If implementation needs an argument not From a1217baaa841553cc3378e425f3cbfeda1356039 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 18:46:58 -0400 Subject: [PATCH 07/15] spec: scope down portable core contracts --- work/portable-core-contracts/spec.md | 1287 ++++++++++---------------- 1 file changed, 498 insertions(+), 789 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 9a3f482..1afbb88 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -5,174 +5,112 @@ drafted: 2026-08-28 # Spec: portable core contracts -Define one small, vendor-neutral JSON contract family and a pure validator for it. -The validator checks canonical bytes, document shape, and relationships among -documents supplied by its caller. It never reads Git, runs an adapter, grants -authority, or changes the live ystack profile. +Define the smallest vendor-neutral record family that later adapters can share. +V1 validates five canonical documents, three executable capabilities, and five +permissions. It does not read Git, run an adapter, authenticate a claim, grant +authority, or perform an external write. ## Requirements -- **R1 — one bounded concern.** This initiative owns canonical records and pure - validation only. It does not resolve a profile from Git, execute a fake or real - adapter, run the 2×2 matrix, authenticate a record, grant a permission, publish a - change, or activate a profile. The current Claude Code, Codex, and GitHub path - keeps working unchanged. -- **R2 — seven document kinds.** Version 1 accepts exactly `stage_request`, - `stage_result`, `adapter_manifest`, `profile`, `resolved_profile`, - `adapter_contract_test_inventory`, and `adapter_contract_test_result`. These are - the only top-level core documents. Evidence stays inside a stage result. Policy, - selection, qualification, grant, gate, and inventory-acceptance records stay - outside the core and enter only through typed immutable references. -- **R3 — strict canonical JSON.** Every document is UTF-8 JSON whose complete bytes - equal the pinned jq 1.6 single-root canonicalizer defined in Design, followed by - one line feed. Reject a BOM, invalid UTF-8, an empty stream, more than one root - JSON value, - duplicate keys, alternate escaping or whitespace, floats, negative integers, - `null`, unknown fields, and non-canonical bytes. The full canonical envelope is - what a document digest covers. -- **R4 — fixed resource limits.** Reject a document over 1,048,576 bytes, nesting - deeper than 32, an object or array with more than 256 members, a decoded string - over 8,192 UTF-8 bytes, or an integer outside `0..2147483647`. Arrays that mean a - set are sorted by their stable ID and contain no duplicates. Arrays whose order - has meaning are named as ordered lists in this spec. -- **R5 — explicit versioning and inert extensions.** Every envelope has exactly - `schema_version: 1`, `kind`, `id`, `body`, and optional `extensions`. Unknown - kinds, versions, and core fields fail closed. Extensions are allowed only at the - top level. Their keys use a lowercase reverse-domain prefix and `/leaf`; each - value is an object. They are preserved in canonical bytes but cannot fill a core - field, add a role/capability/permission, change a result, choose a tool or policy, - or reach an executor or publisher. Any new core field, including an optional - field, or changed enum meaning requires a new major schema version. -- **R6 — references are claims, not trust.** The shared reference shapes in Design - use full digests and safe logical IDs. Pure validation checks their syntax and - equality only. A valid ref does not prove that content or a Git object exists, - that a repository ID maps to the right repository, that an actor is authentic, - or that a policy, selection, qualification, grant, gate decision, evidence - verdict, or inventory acceptance is authoritative. -- **R7 — one operation per request.** A stage request names one resolved binding, - one v1 capability, its exact effective permission set, and the capability's exact - argument object. It also binds the initiative, workflow, stage, task class, - target/source/input claims, risk claim, profile and environment, finish condition, - verification instructions, required evidence kinds, and any caller-supplied - selection, qualification, grant, or gate-decision refs. Retry attempts retain the - same exact request document ref by later orchestrator policy; this pure validator - checks one attempt and does not enforce retry sequence. A request cannot express merge, approval, bypass, human - impersonation, policy activation, arbitrary command execution, or generic network - access. -- **R8 — total result rules.** A stage result binds one exact request and one - attempt. It records the controller, terminal status, whether the operation ran, - the outcome when allowed, reason and diagnostics, expected/observed identities, - outputs, evidence, actual performer, binding, environment, and capability when - executed, actual execution facts, and ordered times. Every status/outcome combination is defined - in Design; all others fail. `completed` means the operation finished, not that it - passed. -- **R9 — actual execution facts stay separate from requested config.** An executed - result records its observed adapter/package/config, environment, tools, and either - deterministic or model execution metadata. Every possibly hidden actual model - fact—provider, model, snapshot, effort, prompt, skills, tools, trace, usage, and - cost—uses the same typed availability union. Missing facts are `unavailable` with - a reason, never guessed or copied from the profile. Failed, cancelled, or - completed-inconclusive execution preserves any observed performer, binding, - environment, or capability mismatch instead of rewriting it to the request. An - unexecuted result - cannot carry a performer, used capability, execution metadata, evidence, or a - successful output. -- **R10 — evidence is exact and non-transferable.** Each evidence record carries the - complete `stage_request` document ref, attempt ID/number, finish condition, every - enclosing output ref, resolved profile, qualification scope when present, - observed environment, performer, actual binding/package/config, used capability, - exact mismatch set, verification instructions, - proof bytes, kind, and verdict. A completed result covers exactly the evidence - kinds requested before execution. Evidence from another request body, attempt, - output, profile, environment, or instruction version cannot be replayed as current - proof even if a stable request ID was reused. -- **R11 — risk and authority references stay separate.** Risk is a claim containing - a tier, reason IDs, policy ref, and required gate refs. Selection, repository - context, qualification, grant, policy, authority, gate decision, and inventory - acceptance use distinct `scope_ref.purpose` values and cannot substitute for one - another. There is no core `trusted`, `authorized`, `qualified`, `approved`, or - `active` boolean. -- **R12 — offer, request, grant, and use never collapse.** A manifest offers roles, - capabilities, permissions, and execution kinds. A profile requests a subset. A - resolved profile records the resolver's claimed deterministic result. An external - accepted policy/permission record may grant. An executed result records actual - use. Validation never infers one step from another. -- **R13 — protected roles stay separate.** Producer, verifier, reviewer, and - publisher bindings are pairwise different in binding ID, adapter instance, - principal, and execution boundary. Authority record IDs and scope digests also - differ when present. The same implementation may serve more than one role only - through those distinct bindings. Producer has no publish permission; verifier has - no model or forge-write permission; reviewer is exact-change read-only; publisher - has no model or candidate-execution permission; a human decision is not an - adapter role or capability. -- **R14 — every v1 capability is closed.** The registry in Design is exhaustive. - Every capability has one role, one outcome family, an exact argument shape, exact - permissions, and a closed allowed evidence set with required members. Unknown or - extra argument fields fail. - There is no wildcard, prefix match, alias, generic shell/argv/env/URL/API, - credential read, generic filesystem write, force/delete, merge, approval, bypass, - deploy, or policy-activation capability. -- **R15 — profile provenance is resolver-produced.** A profile contains requested - bindings and immutable refs, but no source ref, selection, trust root, grant, - qualification, gate, or activation claim. `resolved_profile` carries the profile - and manifest document refs plus source-value, selection, and repository-context - claims produced by the later resolver. Core validation compares supplied - documents and subsets but never reads a repository or accepts self-declared - profile provenance. -- **R16 — test expectations and observations are different records.** The inventory - alone owns expected phase, verdict/error, equivalence group, fixture digest, and - assertion IDs. The result contains observations only and binds the exact inventory - plus an external inventory-acceptance ref. Pure validation checks one-for-one case - and assertion sets and a correctly derived overall outcome. It does not execute a - case, authenticate acceptance, or treat a structurally valid pass report as proof. -- **R17 — one pure validator boundary.** `core/v1/contracts.jq` is the only - executable source of shapes, enums, registries, and relational rules. - `scripts/core-contract.sh` exposes only the four pure commands in Design. It does - not read Git, physical repository mappings, raw fixtures, network, environment - config, executables, or credentials. Success is exit 0 with empty stdout. Failure - is nonzero, emits one stable error code first, and never echoes untrusted input. -- **R18 — adversarial proof and delivery.** Hermetic tests cover every valid kind, - every capability argument shape, and the rejection matrix in Design. Tests derive - mutations from small valid baselines; they do not run an adapter or use a network. - New load-bearing files enter `ci/required-files.txt`; README states the contract - and that it is not live-wired. CI runs the suite with checksum-pinned jq 1.6. - An operator-driven implementation may update `.github/workflows/ci.yml`; an - unattended implementation must submit that constitution change under - `proposals/` for operator application. -- **R19 — normal review size.** The implementation budget is at most 220 net lines - for `contracts.jq`, 45 for the wrapper, 100 for table-driven tests, and 35 for - docs, manifest, and CI: 400 net lines total. The plan must validate these estimates - before code. If it cannot meet them without dropping a requirement, return to G2 - and reduce or split v1. This spec does not accept the old #154 size exception. +- **R1 — five documents.** V1 accepts `adapter_manifest`, `profile`, + `resolved_profile`, `stage_request`, and `stage_result`. Evidence is nested in a + result. Policy, qualification, grant, gate, adapter-test, and telemetry bodies + remain outside core and enter only by immutable ref. +- **R2 — three capabilities.** V1 can produce one artifact, verify one candidate, + or review one exact change. Publisher, forge, CI, execution, and identity remain + dormant role IDs with no v1 operation. Unknown/wildcard/prefix/alias capabilities + fail closed. +- **R3 — five bounded permissions.** Target read, scratch write, candidate execute, + evidence write, and conditional model invoke have the exact resource/action bounds + in Design. None grants shell, arbitrary command, environment, general network, + credential, generic file/Git-ref write, approval, merge, bypass, deploy, or human + impersonation. +- **R4 — strict canonical JSON.** A document is exactly one UTF-8 JSON value whose + bytes equal the pinned jq 1.6 single-root canonicalizer plus one line feed. Reject + empty/multi-root streams, BOM, invalid UTF-8, duplicate keys, alternate + whitespace/escaping, floats, negative integers, `null`, unknown fields, and + non-canonical bytes. +- **R5 — fixed limits and versions.** Reject bytes over 1,048,576, depth over 32, an + object/array over 256 members, a decoded string over 8,192 UTF-8 bytes, or an + integer outside `0..2147483647`. Every envelope has exactly + `schema_version:1`, `kind`, `id`, and `body`; v1 has no extensions. Any new field, + kind, capability, permission, or changed enum meaning needs a new major. +- **R6 — refs are claims.** Core checks syntax, canonical digests, and relationships + among caller-supplied documents. It does not prove Git/content existence, + repository mapping, actor identity, evidence truth, or the authority of a policy, + selection, qualification, grant, or gate ref. +- **R7 — offer/request/grant/use stay distinct.** A manifest offers. A profile + requests. A resolved profile records claimed resolution. External policy may + grant. A request names one operation. A result records observed use. No step + implies another; there is no `trusted`, `qualified`, `approved`, or `active` flag. +- **R8 — protected roles stay separate.** Producer, verifier, reviewer, and dormant + publisher bindings differ in binding ID, instance, principal, execution boundary, + and authority scope. One implementation may serve several roles only through + those distinct bindings. A human is not an adapter role or capability. +- **R9 — one closed operation per request.** A request binds one runnable resolved + binding, one capability, its exact permissions/arguments, target and inputs, + risk/gate claims, environment, finish condition, verification instructions, + required evidence kinds, and time. Retry sequence/delivery are later concerns. +- **R10 — total result truth.** A result records one terminal status and attempt, + observed execution facts when work ran, outputs, diagnostics, and evidence. + Completed conclusive execution equals the request. Failed, cancelled, and + completed-inconclusive execution preserves different actual facts without + granting them authority. +- **R11 — evidence is result-bound.** Nested evidence contains only ID, kind, + verdict, and proof ref. The enclosing result binds the exact request/attempt, + outputs/delta, profile/qualification, instructions, expected and observed + execution facts, and time. External users reference `stage_result + evidence ID`; + they never copy or re-sign the nested body. +- **R12 — actual model/tool facts are honest claims.** Actual provider, model, + snapshot, effort, prompt, skills, and tools are recorded/computed/unavailable. + Missing facts carry a reason and are never copied from requested profile values. + Trace, usage, and cost move to telemetry. +- **R13 — pure validation.** One schema source validates canonical bytes, exact + shapes, registries, manifest/profile/resolution relations, and + request/result/status/evidence rules. It never resolves physical repositories, + launches a process, reads a raw fixture, or evaluates policy. +- **R14 — credible delivery bound.** One jq source, one shell front door, + table-driven tests, docs, restore entries, and operator-owned CI wiring must fit + 400 normally formatted net lines. If the plan cannot prove that, return to G2; + no code-golf or size exception is accepted. +- **R15 — no live change.** This work does not activate a profile, extract a real + adapter, regenerate `/yshifu`, alter an open session, or enable autonomous writes. + The operator remains the only merge authority. ## Design -### Canonical envelope and primitive rules +### Canonical notation and primitives -Core IDs match `^[a-z0-9][a-z0-9._:-]{0,127}$`. SHA-256 values are 64 lowercase -hex characters. Versions are exact IDs, never ranges. Optional fields are omitted; -`null` is always invalid. +Objects are exact; unlisted fields fail. `T?` is omitted-or-present, never `null`. +`set(key,min..max)` is sorted and unique by `key`. `present` is exactly +`{state:"present",value:T}` or `{state:"absent"}`. -An extension key has two or more lowercase DNS labels, each 1–63 characters with -no leading or trailing hyphen, then `/`, then a 1–128 character lowercase leaf made -from letters, digits, `.`, `_`, or `-`. For example, `com.example/trace` is valid; -`trace`, `Com.example/x`, and `com..example/x` are not. +Core `ID` matches `^[a-z0-9][a-z0-9._:-]{0,127}$`. `Int` is +`0..2147483647`. `SHA256` is 64 lowercase hex. `Version` is an exact ID. +`ShortText` is 1–1,024 decoded UTF-8 bytes and is never authority. `Time` is a +real UTC second-level `YYYY-MM-DDTHH:MM:SSZ`. `MediaType` is a lowercase +`type/subtype` token at most 127 characters. `GitOID` is 40 lowercase hex for +SHA-1 or 64 for SHA-256. `git-key` is the lexical tuple +`(repository_id,hash_algorithm,commit_id,location kind/value,object_type,object_id,mode)`. +`selector-tuple` is `(kind,input_id-or-scope_sha256-or-empty)`. Both are compared +directly by jq; no nested SHA computation is required. +`ReverseDNS` has at least two lowercase DNS labels; each label is 1–63 alphanumeric +or interior-hyphen characters with no edge hyphen. -The wrapper checks the byte limit before parsing. It invokes pinned jq 1.6 as -`jq -s -S -c 'if length == 1 then .[0] else error("root-count") end'`, which rejects -empty and multi-value streams before selecting the one root and emitting canonical -bytes. It compares those bytes with the original, then applies depth, member, -string, integer, shape, and relational checks. The byte comparison makes a duplicate -key or alternate escape non-canonical even though jq would otherwise keep only the -final decoded key. Regression tests protect that boundary. +Document kinds are the five in R1. `AdapterRole` is `producer`, `verifier`, +`reviewer`, `publisher`, `forge`, `ci`, `execution`, or `identity`. `ActorRole` +adds `operator`, `manager`, `orchestrator`, and `observer`; those additions cannot +be profile bindings or operation performers. Operator attribution is not approval. -### Normative schema notation and shared shapes +The wrapper checks byte size, then runs: -All objects below are exact: a field not listed is invalid. `T?` means the field may -be omitted; `set(key,min..max)` is a JSON array sorted by `key`, unique by that -key, and within the stated cardinality; `list(min..max)` preserves order. -`present` is exactly `{state:"present",value:T}` or `{state:"absent"}`. -`availability` is exactly one of: +```text +jq -s -S -c 'if length == 1 then .[0] else error("root-count") end' +``` + +It compares emitted bytes plus one line feed with input before shape checks. + +Actual facts use exactly: ```text {state:"recorded",value:T,source_ref:content_ref} @@ -181,659 +119,430 @@ key, and within the stated cardinality; `list(min..max)` preserves order. {state:"not-applicable"} ``` -`ShortText` is 1–1,024 decoded UTF-8 bytes and is always untrusted prose. `Time` is -the UTC value defined below. `Version` is an exact core ID. `MediaType` is a -lowercase media type matching -`^[a-z0-9][a-z0-9!#$&^_.+-]{0,62}/[a-z0-9][a-z0-9!#$&^_.+-]{0,62}$`. -`GitOID` is 40 -lowercase hex characters for SHA-1 or 64 for SHA-256. `RepoPath` follows the lexical -rules below. `TopicRef` is `refs/heads/` plus slash-separated 1–64 character -segments made from lowercase letters, digits, `.`, `_`, or `-`; no segment is `.`, -`..`, begins or ends with `.`, ends with `.lock`, or contains `..`. -`IdempotencyKey` is an `ID`. -`ExtensionPrefix` is the reverse-domain portion of the extension-key grammar. -`canonical-sha256` as a set key means SHA-256 of the nested value's canonical JSON; -`source-canonical-sha256` means SHA-256 of only a `source_value_ref.source` object; -`value` means the primitive string itself. -`ErrorCode` matches `^E_[A-Z][A-Z0-9_]{0,62}$`; it is not a lowercase core `ID`. - -The adapter-role registry is exactly `producer`, `verifier`, `reviewer`, `forge`, -`ci`, `execution`, `identity`, and `publisher`. Actor roles may also be `operator`, -`manager`, `orchestrator`, or `observer`; those four can request/report but cannot -be an adapter binding or operation performer. Human approval is not an actor role. -An `operator` actor ref is attribution only and never a human decision record. - -| Shape | Exact object | +This union is named `Fact`. + +### Shared refs + +| Shape | Exact fields | |---|---| -| `document_ref(K?)` | `{schema_version:1,kind:DocumentKind,id:ID,sha256:SHA256}`; when `K` is supplied, `kind=K` | -| `git_revision_ref` | `{repository_id:ID,hash_algorithm:"sha1"|"sha256",commit_id:GitOID}` with matching hash length | -| `git_object_ref` | `{revision:git_revision_ref,path:RepoPath,object_type:"blob"|"tree",object_id:GitOID,mode:"100644"|"100755"|"040000"}`; object ID length matches `revision.hash_algorithm`; tree requires `040000`, blob requires `100644|100755` | -| `content_ref` | `{content_id:ID,media_type:MediaType,sha256:SHA256}`; the ID is not a URL or host path | +| `document_ref(K?)` | `{schema_version:1,kind:DocumentKind,id:ID,sha256:SHA256}`; `kind=K` when constrained | +| `git_revision_ref` | `{repository_id:ID,hash_algorithm:"sha1"|"sha256",commit_id:GitOID}`; full 40/64-char ID matches algorithm | +| `git_location` | `{kind:"root"}` or `{kind:"path",value:RepoPath}` | +| `git_object_ref` | `{revision:git_revision_ref,location:git_location,object_type:"blob"|"tree",object_id:GitOID,mode:"100644"|"100755"|"040000"}`; root requires tree; OID length/mode match algorithm/type | +| `content_ref` | `{content_id:ID,media_type:MediaType,sha256:SHA256}`; ID is not URL/path; core never dereferences | | `artifact_ref` | `{type:"git-object",value:git_object_ref}` or `{type:"content",value:content_ref}` | +| `input_ref` | `{type:"artifact",value:artifact_ref}` or `{type:"document",value:document_ref}` | | `evidence_ref` | `{stage_result_ref:document_ref(stage_result),evidence_id:ID}` | -| `input_value_ref` | tagged `{type:"artifact",value:artifact_ref}` or `{type:"document",value:document_ref}`; prior evidence has its own field | -| `claim_value_ref` | tagged `{type:"artifact",value:artifact_ref}`, `{type:"document",value:document_ref}`, `{type:"git-revision",value:git_revision_ref}`, `{type:"scope",value:scope_ref}`, or `{type:"environment",value:environment_ref}` | -| `source_value_ref` | `{source:git_object_ref,value_format:"raw-bytes"|"canonical-json",value_sha256:SHA256}` | -| `scope_subject_ref` | tagged `{type:"document",value:document_ref}` or `{type:"artifact",value:artifact_ref}` | -| `scope_ref(P?)` | `{purpose:ScopePurpose,decision_record_ref:content_ref,subject_ref:scope_subject_ref,scope_sha256:SHA256}`; when `P` is supplied, `purpose=P` | +| `scope_subject` | `{type:"artifact",value:artifact_ref}` or `{type:"document",value:document_ref}` | +| `scope_ref(P?)` | `{purpose:ScopePurpose,decision_record_ref:content_ref,subject_ref:scope_subject,scope_sha256:SHA256}`; purpose=P when constrained | | `actor_ref` | `{role:ActorRole,implementation_id:ID,implementation_version:Version,adapter_instance_id:ID,principal_id:ID,execution_boundary_id:ID,authority_ref?:scope_ref(authority)}` | | `environment_ref` | `{environment_id:ID,fingerprint_sha256:SHA256}` | -| `tool_ref` | `{tool_id:ID,tool_version:Version,package_ref:artifact_ref,config_ref:present}` | -| `change_ref` | `{repository_id:ID,base:git_revision_ref,head:git_revision_ref,delta_ref:content_ref}`; both revisions use `repository_id` | - -`ScopePurpose` is exactly `selection`, `repository-context`, `qualification`, -`grant`, `policy`, `authority`, `gate-requirement`, `gate-decision`, `inventory-acceptance`, -`config-contract`, `output-contract`, `allowed-delta`, `verification-plan`, -`review-policy`, `check-set`, `environment-policy`, `publisher-policy`, -`finish-condition`, or `verification-instructions`. A field expecting one purpose -rejects every other purpose. - -A `RepoPath` has no leading or trailing slash, empty segment, `.` or `..` segment, -backslash, NUL, or ASCII control character. These are lexical checks only. The -profile resolver owns physical repository mapping, object existence, mode, symlink -containment, replacement-object disabling, and source provenance. - -For `source_value_ref`, `raw-bytes` means SHA-256 of the exact Git object payload; -`canonical-json` means SHA-256 of the complete canonical JSON bytes and is valid -only for a blob. Core validates the tag, hash, and blob restriction but never reads -those bytes. - -### Exact nested records - -- `model_request` is `{provider_id:ID,model_id:ID,effort_id:ID}`. -- `profile_binding` is exactly `{binding_id:ID,role:AdapterRole, - manifest_ref:document_ref(adapter_manifest),execution_kind:"model"|"deterministic", - adapter_instance_id:ID,principal_id:ID,execution_boundary_id:ID, - authority_ref?:scope_ref(authority),package_ref:git_object_ref, - config_ref?:git_object_ref,prompt_ref?:git_object_ref, - skill_refs:set(canonical-sha256,0..256), - requested_tool_refs:set(tool_id,0..128),model_request?:model_request, - requested_capabilities:set(value,1..256), - requested_permissions:set(value,1..256)}`. `model_request` and - `prompt_ref` are required exactly when `execution_kind=model`. Tool refs inside a - manifest or profile require a `git-object` package ref and, when config is - present, a `git-object` config ref. -- `resolved_binding` is exactly `{binding:profile_binding, - adapter_implementation:{id:ID,version:Version}, - manifest_source:source_value_ref,package_source:source_value_ref, - config_source?:source_value_ref,prompt_source?:source_value_ref, - skill_sources:set(source-canonical-sha256,0..256), - tool_sources:set(source-canonical-sha256,0..256)}`. Optional/source set - presence and source object refs exactly match the binding refs they claim to resolve. - A source object appears at most once in each source set. If the same source object - appears in another resolved field, its value format and digest are identical. - At most 128 requested tools need at most 256 distinct package/config sources. -- `named_input` is `{input_id:ID,value:input_value_ref}`. -- `risk_claim` is `{tier:{namespace:"core",name:"routine"|"high"|"bootstrap"} - |{namespace:ExtensionPrefix,name:ID},reason_ids:set(value,1..256), - policy_ref:scope_ref(policy),required_gate_refs:set - (scope_sha256,0..256)}`. A non-core tier is inert. -- `operation` is `{role:AdapterRole,binding_id:ID,capability_id:CapabilityID, - permissions:set(value,1..256),arguments:CapabilityArgs}` where the - argument schema is selected only by `capability_id` from the registry below. -- `Outcome` is exactly `{family:"change",value:"changed"|"no-change"| - "inconclusive"}`, `{family:"check",value:"passed"|"failed"|"inconclusive"}`, - or `{family:"advisory",value:"proceed"|"refine"|"drop"|"inconclusive"}`. -- `TerminalStatus` is exactly `completed`, `skipped`, `stale`, `blocked`, `failed`, - or `cancelled`. `EvidenceKind` is exactly `deterministic`, `behavioral`, - `architecture`, or `independent-review`. -- `reason` is `{reason_id:ID,summary?:ShortText}`. Summary text never drives a tool, - status, label, permission, or gate. -- `output_record` is `{output_id:ID,purpose:"subject"|"auxiliary"|"release", - ref:input_value_ref}`. Output sets sort by `output_id`. -- `actual_binding` is `{binding_id:ID, - adapter_implementation:{id:ID,version:Version}, - manifest_ref:document_ref(adapter_manifest), - package_ref:git_object_ref,config_ref?:git_object_ref,execution_kind:"model"| - "deterministic",adapter_instance_id:ID,principal_id:ID, - execution_boundary_id:ID,authority_ref?:scope_ref(authority)}`. -- `execution_mismatch` is exactly - `{field:"performer",expected:actor_ref,observed:actor_ref}`, - `{field:"binding",expected:actual_binding,observed:actual_binding}`, or - `{field:"environment",expected:environment_ref,observed:environment_ref}`, or - `{field:"capability",expected:CapabilityID,observed:CapabilityID}`. - Expected is derived from the request/resolved binding, observed equals the result, - and the two canonical values differ. -- `usage_value` is `{input_tokens:Int,output_tokens:Int,cache_read_tokens:Int, - cache_write_tokens:Int}`. `cost_value` is `{currency_id:ID,microunits:Int}`. -- `execution_metadata` is exactly `{kind:"model"|"deterministic", - provider:availability,model:availability,snapshot:availability, - effort:availability,prompt:availability, - skills:availability(canonical-sha256,0..256)>, - tools:availability(tool_id,0..128)>,trace:availability, - usage:availability,cost:availability}`. Deterministic - execution requires provider/model/snapshot/effort/prompt/skills to be - `not-applicable`; model execution requires each of those fields to be recorded, - computed, or unavailable, never not-applicable. -- `evidence_record` is exactly `{evidence_id:ID, - request_ref:document_ref(stage_request),attempt_id:ID,attempt_number:Int, - finish_condition_ref:scope_ref(finish-condition), - resolved_profile_ref:document_ref(resolved_profile), - qualification_ref?:scope_ref(qualification),environment_ref:environment_ref, - performer:actor_ref,actual_binding:actual_binding,used_capability:CapabilityID, - execution_mismatches:set(field,0..4), - verification_instruction_ref:scope_ref(verification-instructions), - outputs:set(output_id,0..256),delta_ref?:content_ref, - kind:EvidenceKind, - verdict:"passed"|"failed"|"inconclusive",proof_ref:content_ref}`. -- `stale_selector` is `{kind:"target"|"source"|"base"|"selection"| - "repository-context"|"qualification"|"grant"|"environment"| - "resolved-profile"}`, `{kind:"input",input_id:ID}`, or - `{kind:"gate-decision",scope_sha256:SHA256}`. It can name only a baseline that - exists as a field or set member in the request. -- `stale_comparison` is `{selector:stale_selector, - expected:present,observed:present}`. Validator - derives `expected` from the selected request baseline and requires exact equality. - Observed uses the same semantic type when present: same document kind; same - artifact variant; same Git repository/hash algorithm/path/object type/mode, or - same content ID/media type; same document kind/schema/ID; same scope purpose and - decision-record/content identity plus subject logical identity; or same - environment ID. Presence or canonical value must differ. -- `test_assertion_result` is `{assertion_id:ID,passed:Boolean}`. -- `test_case` is `{case_id:ID,phase:TestPhase,fixture_ref:artifact_ref, - expected_status:"accepted"|"rejected"|"transport-failed", - expected_error_code?:ErrorCode,equivalence_group?:ID, - assertion_ids:set(value,1..256)}`. Error is forbidden for `accepted` and - required otherwise. -- `test_observation` is `{case_id:ID,phase:TestPhase, - observed_status:"accepted"|"rejected"|"transport-failed"|"inconclusive", - observed_error_code?:ErrorCode,produced_document_refs:set(sha256,0..256), - assertions:set(assertion_id,1..256)}`. Error is forbidden - for accepted and required otherwise. - -### Exact top-level document bodies - -The envelope `id` is respectively the adapter ID, profile ID, resolved-profile ID, -stable request ID, result ID, inventory ID, or contract-test-result ID. Body fields -do not repeat it. - -- `adapter_manifest.body` is `{adapter_version:Version,protocol_version: - "core-stage/v1",package_ref:git_object_ref, - offered_roles:set(value,1..8), - offered_execution_kinds:set<"model"|"deterministic">(value,1..2), - offered_capabilities:set(value,1..256), - offered_permissions:set(value,1..256), - offered_tool_refs:set(tool_id,0..128), - config_contract_ref?:scope_ref(config-contract)}`. -- `profile.body` is `{profile_version:Version, - bindings:set(binding_id,1..8)}` with at most one binding per - adapter role. It contains no source, selection, trust, grant, qualification, - gate, or activation field. -- `resolved_profile.body` is `{profile_ref:document_ref(profile), - profile_source:source_value_ref,selection_ref:scope_ref(selection), - repository_context_ref:scope_ref(repository-context), - bindings:set(binding.binding_id,1..8)}`. Profile source uses - `canonical-json` and its value digest equals `profile_ref.sha256`. -- `stage_request.body` is `{initiative_id:ID,workflow_id:ID,stage_id:ID, - task_class_id:ID,requested_by:actor_ref,target_ref:git_revision_ref, - source:present,base:present, - inputs:set(input_id,0..256), - prior_evidence_refs:set(stage_result_ref.sha256+evidence_id,0..256),risk:risk_claim, - resolved_profile_ref:document_ref(resolved_profile), - selection_ref:scope_ref(selection), - repository_context_ref:scope_ref(repository-context), - qualification_ref?:scope_ref(qualification),grant_ref?:scope_ref(grant), - gate_decision_refs:set(scope_sha256,0..256), - environment_ref:environment_ref,operation:operation, - finish_condition_ref:scope_ref(finish-condition), - verification_instruction_ref:scope_ref(verification-instructions), - required_evidence_kinds:set(value,1..4),requested_at:Time}`. -- `stage_result.body` is `{request_ref:document_ref(stage_request), - resolved_profile_ref:document_ref(resolved_profile),attempt_id:ID, - attempt_number:Int,reported_by:actor_ref,executed:Boolean,status:TerminalStatus, - outcome?:Outcome,reason?:reason, - stale_comparisons?:set(canonical-sha256,1..256), - outputs:set(output_id,0..256),delta_ref?:content_ref, - diagnostics:set(content_id,0..256),performer?:actor_ref, - used_capability?:CapabilityID,actual_binding?:actual_binding, - actual_environment_ref?:environment_ref, - execution_mismatches?:set(field,0..4), - execution_metadata?:execution_metadata, - evidence:set(evidence_id,0..256),started_at?:Time, - finished_at?:Time,recorded_at:Time}`. Presence follows the total status table. -- `adapter_contract_test_inventory.body` is `{inventory_version:Version, - cases:set(case_id,1..256)}`. -- `adapter_contract_test_result.body` is `{inventory_ref: - document_ref(adapter_contract_test_inventory),inventory_acceptance_ref: - scope_ref(inventory-acceptance),runner:actor_ref,environment_ref:environment_ref, - execution_metadata:execution_metadata,started_at:Time,finished_at:Time, - recorded_at:Time,overall:"passed"|"failed"|"inconclusive", - observations:set(case_id,1..256)}`. Runner role is `verifier`, - execution metadata is deterministic, and `started_at <= finished_at <= recorded_at`. - -The resolver must not copy a profile-declared source because no such field exists. -`validate-profile-set` recomputes document refs from supplied canonical files and -checks every binding, source presence, subset, and equality rule above. It cannot -prove any Git read. - -### Status, outcome, and evidence rules - -Outcome is one tagged union: - -- `change`: `changed`, `no-change`, or `inconclusive`; -- `check`: `passed`, `failed`, or `inconclusive`; -- `advisory`: `proceed`, `refine`, `drop`, or `inconclusive`. - -An advisory is data for a later gate. It is never approval or a gate decision. - -| Status | Executed | Outcome and allowed records | -|---|---:|---| -| `completed` | true | outcome required; full requested evidence required | -| `skipped` | false | no outcome; reason required; no performer/evidence/output | -| `stale` | false | no outcome; reason and one or more differing stale comparisons required; no performer/evidence/output | -| `blocked` | false | no outcome; reason required; diagnostics allowed; no performer/evidence/output | -| `failed` | false | no outcome; machinery reason and diagnostics required; no performer/evidence/output | -| `failed` | true | only an inconclusive outcome; reason, diagnostics, and attempt evidence required; no successful subject output or delta | -| `cancelled` | false | no outcome; reason required; no performer/evidence/output | -| `cancelled` | true | only an inconclusive outcome; reason and attempt evidence required; no successful subject output or delta | - -Outcome is present exactly when `executed=true`. Performer, used capability, -actual binding, actual environment, execution-mismatch set, execution metadata, -start time, and finish time are also present exactly when executed. Reason is -required for every non-completed status and every -inconclusive outcome; it is forbidden for a completed non-inconclusive outcome. -`stale_comparisons` is present exactly for `stale`; every expected value is derived -from its request selector, every observed value is type-compatible, and every pair -differs. Diagnostics are non-empty for -`failed`, may be non-empty for `blocked` or `cancelled`, and are empty otherwise. - -Every result has `recorded_at`. An unexecuted result omits start/finish times and -requires `request.requested_at <= recorded_at`. An executed result requires -`requested_at <= started_at <= finished_at <= recorded_at`. Times are real UTC -calendar values in exact second-level `YYYY-MM-DDTHH:MM:SSZ` form. Attempt number -must be at least 1. Monotonic attempt numbering, duplicate delivery, and retry -continuity require history and belong to the durable orchestrator. - -Only `change/changed` may have subject or release outputs and a delta ref. It -requires at least one subject output and the delta. -`change/no-change` and `change/inconclusive` forbid both. Auxiliary artifacts are -allowed only on completed outcomes and are bound by every evidence record. -Diagnostics never satisfy requested evidence. - -Evidence kinds are exactly `deterministic`, `behavioral`, `architecture`, and -`independent-review`; verdicts are `passed`, `failed`, and `inconclusive`. A -completed result has at least one evidence record for every requested kind, no -unrequested kind, and unique evidence IDs. `check/passed`, `change/changed`, -`change/no-change`, and non-inconclusive advisory outcomes require all requested -evidence to pass. For a completed result, failed evidence takes precedence: a check-family result is -`check/failed`; a change/advisory result is its family's `inconclusive`. If there is -no failed evidence but at least one inconclusive verdict, every family uses its -`inconclusive` value. `check/failed` therefore requires at least one failed verdict -and may retain inconclusive verdicts. A family-inconclusive outcome requires at -least one failed or inconclusive verdict. No true verdict is discarded. - -For `failed|cancelled + executed=true`, evidence is non-empty, its kinds are a -subset of the request's required kinds, every verdict is `failed` or -`inconclusive`, at least one is non-passing, and its output set is empty. It records only the interrupted attempt and cannot satisfy a -later completed result. An unexecuted result has an empty evidence set. - -An exact performer mismatch on a reviewer request has one narrow incident rule. If -the observed performer is not a reviewer, the failed, cancelled, or -completed-inconclusive attempt may still carry the requested `independent-review` -kind with only `failed|inconclusive` verdicts and the observed performer. This -records that independent review did not occur; it is never passing review evidence, -cannot satisfy a completed non-inconclusive result, and cannot be reused to satisfy -a later attempt. - -`validate-stage-run` recomputes the complete request document ref and requires it -to equal the result and every nested evidence `request_ref`. Evidence attempt values -equal the enclosing result. Finish condition, profile, qualification presence, -verification instructions, every output record, and delta presence/value equal the -request/result values exactly. Every evidence record on a completed result covers -the complete output set. Prior-stage evidence -appears only in `request.prior_evidence_refs`; its body is never copied into the new -result. - -For every executed result, the validator derives the expected performer and -`actual_binding` projection from the selected resolved binding, and takes the -expected environment and capability from the request. A completed -non-inconclusive result requires all four observed values to equal those expectations -and has an empty mismatch set. An executed `failed` or `cancelled` result, or a completed-inconclusive result, -may differ. Its mismatch set contains exactly one record for each differing field, -no equal or missing field, with expected derived from the request/binding and -observed equal to the result's actual value. The set may be empty when failure or -inconclusive outcome has another cause. - -Execution metadata kind always matches the observed actual binding. Every nested -evidence record repeats the result's observed performer, actual binding, actual -environment, used capability, and complete mismatch set. The request ref in that -evidence preserves -the expected values. These are equality checks over claims, not identity proof. - -Every possibly hidden model fact uses `availability` as defined above. A profile -model request is desired configuration only and cannot fill an actual result field. - -### Capability, permission, and argument registry - -The permission registry is the union of the full IDs in this table plus the -conditional `core.perm.model.invoke.v1`. No short name is an alias. Every argument -object has exactly the fields and types shown. `D`, `B`, `A`, and `R` mean -deterministic, behavioral, architecture, and independent-review evidence. -`core.perm.record.read.v1` reads only canonical core documents named by supplied -`document_ref`s; `core.perm.content.read.v1` reads only bytes named by supplied -`content_ref`s. Neither permits path, directory, database, generic network, or -credential reads. - -| Role / capability | Exact arguments | Exact base permissions | Outcome / allowed evidence; required | +| `tool_ref` | `{tool_id:ID,tool_version:Version,package_ref:git_object_ref,config_ref:present}` | +| `change_ref` | `{repository_id:ID,base:present,head:git_revision_ref,delta_ref:content_ref}`; revisions match repository | +| `source_value_ref` | `{source:git_object_ref,value_format:"raw-bytes"|"canonical-json",value_sha256:SHA256}` | + +`RepoPath` is a non-empty repo-relative POSIX path with no empty, `.`, `..`, +backslash, NUL, or control segment. Root tree is `git_location:{kind:"root"}`, never +an empty path. Symlink/gitlink modes are not representable; physical checks belong +to the resolver. + +Scope purposes are `selection`, `repository-context`, `qualification`, `grant`, +`policy`, `authority`, `gate-requirement`, `gate-decision`, `config-contract`, +`output-contract`, `allowed-delta`, `verification-plan`, `review-policy`, +`finish-condition`, and `verification-instructions`. Scope refs carry identity only; +v1 never forwards their bytes as commands or permissions. + +### Manifest, profile, and resolved profile + +Capabilities are exactly `core.harness.produce.v1`, `core.verify.run.v1`, and +`core.review.change.v1`. Permissions are exactly the five defined below. + +`adapter_manifest.body` is: + +```text +{adapter_version:Version,package_ref:git_object_ref, + offered_roles:set(value,1..8), + offered_execution_kinds:set<"model"|"deterministic">(value,1..2), + offered_capabilities:set(value,0..3), + offered_permissions:set(value,0..5), + offered_tools:set(tool_id,0..32), + config_contract_ref?:scope_ref(config-contract)} +``` + +`profile_binding` is: + +```text +{binding_id:ID,role:AdapterRole,manifest_ref:document_ref(adapter_manifest), + execution_kind:"model"|"deterministic",adapter_instance_id:ID,principal_id:ID, + execution_boundary_id:ID,authority_ref?:scope_ref(authority), + package_ref:git_object_ref,config_ref?:git_object_ref,prompt_ref?:git_object_ref, + skill_refs:set(git-key,0..32), + requested_tools:set(tool_id,0..32),model_request?:model_request, + requested_capabilities:set(value,0..1), + requested_permissions:set(value,0..5)} +``` + +`model_request` is `{provider_id:ID,model_id:ID,effort_id:ID}`. Producer/reviewer may +be model-backed. Every model binding requires model request and prompt; every +deterministic binding forbids both. Verifier and all dormant roles are deterministic. +Producer, verifier, reviewer, and publisher bindings occur exactly once. Other roles +are optional and unique by role. + +Producer requests only the producer capability; verifier only verifier; reviewer +only reviewer. Dormant roles request zero capabilities/permissions and cannot be +selected by a stage operation. This records compatibility/separation, not activation +or grant. The four protected bindings require authority refs; their binding, +instance, principal, execution-boundary, and authority scope digests are pairwise +different. + +`profile.body` is `{profile_version:Version, +bindings:set(binding_id,4..8)}` and contains no source, selection, +grant, qualification, gate, trust, or activation field. + +`resolved_binding` is: + +```text +{binding:profile_binding,adapter_implementation:{id:ID,version:Version}, + manifest_source:source_value_ref,package_source:source_value_ref, + config_source:present,prompt_source:present, + skill_sources:set(git-key,0..32), + tool_sources:set<{tool_id:ID,package_source:source_value_ref, + config_source:present}>(tool_id,0..32)} +``` + +`resolved_profile.body` is: + +```text +{profile_ref:document_ref(profile),profile_source:source_value_ref, + selection_ref:scope_ref(selection),repository_context_ref:scope_ref(repository-context), + bindings:set(binding.binding_id,4..8)} +``` + +Profile/manifest sources use `canonical-json` and their value digests equal the +document refs. Each resolved `binding` equals its profile binding, and its manifest +ref selects exactly one supplied manifest. Package/config/prompt/skill/tool source +objects and presence equal their corresponding binding or tool refs; skill/tool +source sets are one-to-one with those refs. A resolved implementation ID/version +equals its manifest document ID/adapter version. Across the resolved profile, one +exact source object has only one format/digest claim; a set cannot contain the same +`git-key` twice. + +`validate-profile-set` recomputes supplied document refs and enforces exact manifest +set, binding-ID set, role/execution offer, package/config/tool relations, capability +subsets, permission unions, source relations, and pairwise protected-role +identity/boundary/authority separation. These are claim relations; the resolver owns +Git/repository/object/provenance truth. + +### Three capabilities and five permissions + +| Capability | Exact arguments | Exact permissions | Outcome / evidence | |---|---|---|---| -| producer / `core.harness.plan.v1` | `{output_contract_ref:scope_ref(output-contract)}` | `core.perm.target.read.v1`, `core.perm.scratch.write.v1` | change / `{A}`; A | -| producer / `core.harness.produce.v1` | `{deliverable_kind:"git-patch"|"structured-artifact",allowed_delta_ref:scope_ref(allowed-delta)}` | `core.perm.target.read.v1`, `core.perm.scratch.write.v1` | change / `{D}`; D | -| verifier / `core.verify.run.v1` | `{verification_plan_ref:scope_ref(verification-plan),network_mode:"deny"}` | `core.perm.target.read.v1`, `core.perm.execution.candidate.v1`, `core.perm.evidence.write.v1` | check / `{D,B,A}`; D | -| reviewer / `core.review.check.v1` | `{change_ref:change_ref,review_policy_ref:scope_ref(review-policy)}` | `core.perm.target.read.v1`, `core.perm.evidence.write.v1` | check / `{R}`; R | -| reviewer / `core.review.advise.v1` | `{change_ref:change_ref,review_policy_ref:scope_ref(review-policy)}` | `core.perm.target.read.v1`, `core.perm.evidence.write.v1` | advisory / `{R}`; R | -| forge / `core.forge.observe.v1` | `{observation_kind:ForgeObservation,subject:ForgeSubject}` | `core.perm.forge.read.v1` | check / `{D}`; D | -| ci / `core.ci.observe.v1` | `{repository_id:ID,commit_ref:git_revision_ref,check_set_ref:scope_ref(check-set),required_only:true}` | `core.perm.ci.read.v1` | check / `{D}`; D | -| execution / `core.execution.provision.v1` | `{environment_spec_ref:scope_ref(environment-policy),input_snapshot_ref:git_object_ref,network_mode:"deny",tool_refs:set(tool_id,0..128)}` | `core.perm.execution.provision.v1` | check / `{D}`; D | -| identity / `core.identity.resolve.v1` | `{subject_actor_ref:actor_ref,purpose:"performer"|"verifier"|"reviewer"|"publisher"|"observer"}` | `core.perm.identity.read.v1` | check / `{D}`; D | -| publisher / `core.publish.branch-bounded.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),topic_ref:TopicRef,expected_old_tip:present,new_commit_ref:git_revision_ref,delta_ref:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.topic-ref.write.v1` | change / `{D}`; D | -| publisher / `core.publish.change-request.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),source_result_ref:document_ref(stage_result),head_ref:{name:TopicRef,commit:git_revision_ref},base_ref:{name:TopicRef,commit:git_revision_ref},title_ref:content_ref,body_ref:content_ref,draft:Boolean,idempotency_key:IdempotencyKey}` | `core.perm.target.read.v1`, `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.change-request.write.v1` | change / `{D}`; D | -| publisher / `core.publish.comment.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),subject:{kind:"issue"|"change-request",id:ID},source_result_ref:document_ref(stage_result),body_ref:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.comment.write.v1` | change / `{D}`; D | -| publisher / `core.publish.status.v1` | `{repository_id:ID,publisher_policy_ref:scope_ref(publisher-policy),commit_ref:git_revision_ref,source_result_ref:document_ref(stage_result),context_id:ID,state:"success"|"failure"|"neutral",details_ref?:content_ref,idempotency_key:IdempotencyKey}` | `core.perm.record.read.v1`, `core.perm.content.read.v1`, `core.perm.forge.read.v1`, `core.perm.forge.status.write.v1` | change / `{D}`; D | - -`ForgeObservation` is exactly `repository-identity`, `default-branch`, -`change-request-state`, `approval-state`, `head-base-identity`, `branch-controls`, -or `code-owner-controls`. `ForgeSubject` is `{repository_id:ID}` for repository, -default-branch, branch-control, and code-owner observations, and -`{repository_id:ID,change_id:ID}` for the other three. There is no query, path, URL, -or arbitrary payload. - -Both reviewer capabilities require the same exact `change_ref`; they differ only in -outcome family. Advice about a non-change subject belongs to a later manager or -orchestrator contract, not the reviewer role. - -Scope-ref arguments carry identity and scope only. They do not embed or expose the -referenced bytes, and v1 defines no command that dereferences them. A later trusted -caller/adapter may use an accepted, separately validated policy or instruction -record, but it cannot reinterpret that record as shell, argv, environment, URL, or -an extra capability under this schema. - -Direct work-subject refs equal the request target repository: reviewer changes, -forge subjects, CI commits, a Git execution snapshot, and publisher repository, -head/base/new commits. Change base/head revisions share that repository. Policy, -adapter, package, config, prompt, skill, and tool refs may live in other logical -repositories and instead must match the resolved profile/binding claims. Publisher title, -body, and details refs have media type `text/plain`. -`source_result_ref` appears in the request's named document inputs; pure validation -checks the ref linkage, while the later publisher verifies that the projected bytes -are an allowed output of that result. - -`expected_old_tip` is `absent` or a present commit from the same repository. The -later publisher verifies real branch policy, current tip, ancestry, actual delta, -credential scope, and atomic compare-and-swap. Branch arguments cannot express -force, delete, merge, or bypass. Change-request arguments can only ensure/open a -request; the named head and base refs must still advertise their supplied commits -at the final read. They cannot close, merge, approve, or assign a reviewer. Status is a -projection, not a gate result. A generic label write is deliberately absent because -labels may carry gate state; a later fixed projection needs a new core version. - -Before any grant or execution, the authenticated publisher must validate the exact -publisher-policy record. For status, it queries the forge controls and rejects a -context used by any required, protected, approval, merge, or production gate. For -change-request and comment text, it dereferences the fixed source output and rejects -mention-like control tokens, provider/bot directives, a line whose first non-space -character is `/`, and control characters. It never rewrites unsafe bytes into safe -ones. If the adapter cannot prove these checks, the capability remains ungranted -and cannot execute. Branch, change-request, and status publishers use their exact -`core.perm.forge.read.v1` immediately before the fixed write and abort on an unknown -or moved control state. This narrows but cannot eliminate the read/write race; later -server-side controls remain required. Core validation checks only the policy ref and -closed proposal shape; it does not claim these external checks already happened. - -Only producer and reviewer bindings may use `execution_kind: model`. Their effective -permission set adds exactly `core.perm.model.invoke.v1`; every other role is -deterministic. Request evidence kinds are a subset of the capability's closed -allowed set and include every required kind. Only either review capability may -produce passing `independent-review` evidence. An observed non-reviewer may carry -that kind only under the exact performer-mismatch incident rule above. For every -supplied profile set and stage run, validation enforces: +| `core.harness.produce.v1` | `{artifact_kind:"plan"|"structured-artifact",output_contract_ref:scope_ref(output-contract)}` or `{artifact_kind:"git-patch",allowed_delta_ref:scope_ref(allowed-delta)}` | target.read + scratch.write + evidence.write; model.invoke iff model | change; deterministic only | +| `core.verify.run.v1` | `{candidate_input_id:ID,verification_plan_ref:scope_ref(verification-plan),network_mode:"deny"}`; input is target Git tree | target.read + candidate.execute + evidence.write | check; deterministic required, behavioral/architecture optional | +| `core.review.change.v1` | `{change_ref:change_ref,review_policy_ref:scope_ref(review-policy)}` | target.read + evidence.write; model.invoke iff model | check; independent-review only | + +Permission IDs and full meaning: + +- `core.perm.target.read.v1`: read only exact target Git revisions/objects and exact + change delta named by the request; no ref enumeration, cwd, host path, or other + repository. +- `core.perm.scratch.write.v1`: write only output bytes below a caller-created + disposable scratch root; no target, Git ref, host config, credential, or external + write. +- `core.perm.candidate.execute.v1`: run only the fixed verifier implementation + against the exact target-tree input and referenced verification plan, with network + denied and no candidate-selected command, credential, inherited secret, host + mount, or undeclared tool. Control foundation must enforce this before real use. +- `core.perm.evidence.write.v1`: append only proof content for the current attempt + under scratch; no ordinary output artifact, Git, forge, policy, or prior-result + mutation. +- `core.perm.model.invoke.v1`: perform only the brokered inference call for the exact + resolved model/prompt/skill/tool binding. It grants no general network, tool, file, + or write authority. + +No v1 field can express shell/argv/env/eval, executable manifest, URL/API/query, +generic filesystem/network, credential/secret, Git-ref write, CR/comment/label/status, +force/delete, approval/merge/bypass, policy activation, deploy, or human +impersonation. + +### Stage request + +`named_input` is `{input_id:ID,value:input_ref}`. `risk_claim` is +`{tier:{namespace:"core",name:"routine"|"high"|"bootstrap"}| +{namespace:ReverseDNS,name:ID},reason_ids:set(value,1..256), +policy_ref:scope_ref(policy),required_gate_refs:set +(scope_sha256,0..256)}`. Non-core tiers are inert claims to core. + +`operation` is `{role:AdapterRole,binding_id:ID,capability_id:CapabilityID, +permissions:set(value,1..5),arguments:CapabilityArgs}` where args are +selected only by the capability table. + +`stage_request.body` is: + +```text +{initiative_id:ID,workflow_id:ID,stage_id:ID,task_class_id:ID,requested_by:actor_ref, + target_repository_id:ID,target_revision:present, + source:present,base:present, + inputs:set(input_id,0..256), + prior_evidence_refs:set((stage_result_ref.sha256,evidence_id),0..256), + risk:risk_claim,resolved_profile_ref:document_ref(resolved_profile), + selection_ref:scope_ref(selection),repository_context_ref:scope_ref(repository-context), + qualification_ref?:scope_ref(qualification),grant_ref?:scope_ref(grant), + gate_decision_refs:set(scope_sha256,0..256), + environment_ref:environment_ref,operation:operation, + finish_condition_ref:scope_ref(finish-condition), + verification_instruction_ref:scope_ref(verification-instructions), + required_evidence_kinds:set<"deterministic"|"behavioral"|"architecture"| + "independent-review">(value,1..3),requested_at:Time} +``` + +Request resolved-profile, selection, and repository-context refs equal the supplied +resolved profile. The binding is non-dormant, owns the capability, and requests the +exact effective permissions; this is compatibility, not activation or grant. +Every Git ref in target revision, base, source, inputs, or reviewer change uses +`target_repository_id`; resolved-profile sources are not target inputs. Verifier +candidate ID selects exactly one input whose value is a Git tree. Reviewer change +head equals the present target revision and change base equals request base. An +absent target revision is allowed only for bootstrap producer work. Required +evidence kinds equal the capability rule: producer and reviewer use their one named +kind; verifier includes deterministic and may also request behavioral and +architecture. + +### Stage result and evidence + +`outcome` is `{family:"change",value:"changed"|"no-change"|"inconclusive"}` or +`{family:"check",value:"passed"|"failed"|"inconclusive"}`. `reason` is +`{reason_id:ID,summary?:ShortText}`. `output` is +`{output_id:ID,ref:content_ref}`; every output is +scratch content, never a target Git object/revision or write receipt. + +`actual_binding` is: + +```text +{binding_id:ID,role:AdapterRole,adapter_implementation:{id:ID,version:Version}, + manifest_ref:document_ref(adapter_manifest),package_ref:git_object_ref, + config_ref:present,execution_kind:"model"|"deterministic", + adapter_instance_id:ID,principal_id:ID,execution_boundary_id:ID, + authority_ref?:scope_ref(authority)} +``` + +`observed_capability` is `{kind:"registered",id:CapabilityID}` or +`{kind:"unclassified",id:ID}`. Unclassified is valid only for failed, cancelled, or +completed-inconclusive execution, and its ID is not one of the three registered +capability IDs. + +`execution_metadata` is: ```text -supplied manifest document refs equal the profile binding manifest-ref set -manifest capability roles are a subset of its offered roles -resolved binding-ID set equals the profile binding-ID set exactly -binding role and execution kind are offered by that manifest -binding package ref equals the manifest package ref -binding config ref is allowed only when the manifest has a config-contract ref -every requested capability belongs to the binding role in the core registry -binding requested tools are a subset of manifest offered tools -profile requested capabilities are a subset of manifest offers -profile requested permissions equal the exact union required by its capabilities -profile requested permissions are a subset of manifest offers -resolved binding.binding equals the profile binding -resolved adapter implementation ID/version equal manifest envelope ID/body version -profile/manifest source-value canonical-json digests equal their document refs -package/config/prompt/skill/tool source presence and object refs equal the binding refs -resolved tool-source object-ref set equals the distinct requested tool package/config refs -repeated source objects anywhere in the resolved profile have identical format/digest -request resolved-profile ref equals the recomputed supplied resolved-profile ref -request selection/repository-context refs equal supplied resolved-profile body refs -operation binding ID and role equal the selected resolved binding -operation capability belongs to its resolved binding -execution-provision argument tools are a subset of resolved binding requested tools -operation permissions equal that capability's effective permissions -result request/resolved-profile refs equal the supplied request/profile documents -executed result outcome family equals the request capability registry family -executed result used capability records the observed actual capability -expected performer/binding/environment/capability derive from binding and request -completed non-inconclusive actual values equal expected and mismatch set is empty -failed/cancelled/completed-inconclusive mismatch set exactly covers actual differences -executed result metadata kind equals observed actual-binding execution kind -completed non-inconclusive actual tools are a subset of resolved-binding - requested tools -all evidence performer/binding/environment/capability/mismatch values equal observed result facts -non-reviewer R-kind evidence is non-passing and requires the exact incident performer mismatch +{kind:"model"|"deterministic",provider:Fact,model:Fact,snapshot:Fact, + effort:Fact,prompt:Fact, + skills:Fact(git-key,0..32)>, + tools:Fact(tool_id,0..32)>} ``` -The core validates only the shape of a publisher proposal and these document -relations. It does not make the write safe or authorized. A valid status proposal -can never stand in for a gate decision. - -### Adapter-contract-test records - -`TestPhase` is exactly `parse`, `document`, `profile-set`, `stage-run`, -`adapter-run`, or `matrix`. Expected observation is `accepted`, `rejected`, or -`transport-failed`, with an exact stable `ErrorCode` when applicable. The inventory -case set and each assertion-ID set are non-empty, sorted, and unique. - -Each result observation has case ID, phase, observed status/error, produced document -refs when any, and one boolean result for every inventory assertion ID. It has no -expected fields. Missing, extra, duplicate, relabelled, or phase-mismatched cases or -assertions fail relational validation; missing execution never becomes a smaller -case set. The result is `inconclusive` if any observation is explicitly -`inconclusive`. Otherwise it is `passed` only when every observed status/error and -assertion matches the inventory, including an expected `transport-failed` case; any -complete mismatch or false assertion derives `failed`. - -For a contract-test result, actual tools and trace must be recorded or computed. -If either is unavailable or not-applicable, every observation is `inconclusive` and -the overall result is `inconclusive`; the case set is still complete. - -`inventory_acceptance_ref` has purpose `inventory-acceptance` and its subject equals -the inventory document ref. `EXPECTED_ACCEPTANCE_SCOPE` is a file containing exactly -one canonical `scope_ref(inventory-acceptance)` object without a document envelope; -it uses the same jq canonical bytes, limits, and extension-free shared-shape checks. -`validate-test-records` receives that caller-owned trust-context file and requires -object equality with the result field. This proves linkage only. The later -adapter-test runner must independently -execute every case, revalidate every produced document, verify Git through the -resolver-owned boundary, and bind real runner evidence. Copying expected fields into -observations can still form a structurally valid lie; core validation never calls it -proof. - -### Pure validator interface and errors - -`scripts/core-contract.sh` has exactly these commands: +Deterministic execution requires provider/model/snapshot/effort/prompt/skills to be +`not-applicable`. Model execution requires each to be recorded/computed/unavailable. +For every execution, tools are recorded, computed, or unavailable—never +not-applicable. + +`execution` is `{performer:actor_ref,actual_binding:actual_binding, +environment:environment_ref,used_capability:observed_capability, +metadata:execution_metadata}`. + +`evidence` is exactly `{evidence_id:ID,kind:"deterministic"|"behavioral"| +"architecture"|"independent-review",verdict:"passed"|"failed"|"inconclusive", +proof_ref:content_ref}`. + +`TerminalStatus` is exactly `completed`, `skipped`, `stale`, `blocked`, `failed`, +or `cancelled`. + +`stale_observation` is exactly one of: + +```text +{selector:{kind:"target"},observed:present} +{selector:{kind:"resolved-profile"},observed:present} +{selector:{kind:"qualification"},observed:present} +{selector:{kind:"environment"},observed:present} +{selector:{kind:"input",input_id:ID},observed:present} +{selector:{kind:"gate-decision",scope_sha256:SHA256}, + observed:present} +``` + +Expected is derived from the selected request field/set member and must differ from +observed in presence or value. A present target observation uses the target +repository. A present resolved-profile observation keeps the expected kind/ID; a +present environment keeps the expected environment ID. Input ID selects the exact +request input. Qualification is the request's sole optional qualification. A gate +selector names one exact requested decision by its scope digest. No other selector +or identity rule is inferred. + +`stage_result.body` is: + +```text +{request_ref:document_ref(stage_request),resolved_profile_ref:document_ref(resolved_profile), + attempt_id:ID,attempt_number:Int,reported_by:actor_ref,status:TerminalStatus, + outcome?:outcome,reason?:reason, + stale_observations?:set(selector-tuple,1..256), + outputs:set(output_id,0..256),delta_ref?:content_ref, + diagnostics:set(content_id,0..256),execution?:execution, + evidence:set(evidence_id,0..256),started_at?:Time,finished_at?:Time, + recorded_at:Time} +``` + +Result `request_ref` equals the recomputed supplied request. Result/profile/request +resolved-profile refs all equal the supplied resolved profile. + +| Status | Exact presence and truth rules | +|---|---| +| `completed` | execution, outcome, started/finished required; diagnostics empty and stale absent; exactly one evidence item per requested kind; reason required iff outcome is inconclusive | +| `skipped` | reason required; outputs, diagnostics, evidence empty; every other optional result field absent | +| `stale` | reason plus non-empty differing stale observations; outputs, diagnostics, evidence empty; execution, outcome, delta, and started/finished absent | +| `blocked` | reason required; diagnostics may be empty or non-empty; outputs/evidence empty; execution, outcome, delta, stale, and started/finished absent | +| `failed` | reason and non-empty diagnostics; outputs empty and delta/stale absent; execution optional under the attempt rules below | +| `cancelled` | reason required; diagnostics may be empty or non-empty; outputs empty and delta/stale absent; execution optional under the attempt rules below | + +If failed/cancelled has no execution, outcome and started/finished are absent and +evidence is empty. If it has execution, outcome is the requested capability family's +`inconclusive`, started/finished are present, and evidence is non-empty, uses each +kind at most once from the request-allowed set, and every verdict is failed or +inconclusive. Such evidence cannot satisfy the current or a later attempt. Attempt +number is at least 1. Time order is +`requested <= started <= finished <= recorded` with execution and +`requested <= recorded` without it. Orchestrator owns sequence/history. + +For completed producer results, any non-passing evidence yields +`change/inconclusive` with no output/delta. Otherwise one output yields +`change/changed`; `delta_ref` is present and equals `output.ref` only for `git-patch`, +and is absent for the other artifact kinds. Empty output/delta yields +`change/no-change`. +For completed verifier/reviewer results, output/delta are empty: any failed evidence +yields `check/failed`; otherwise any inconclusive evidence yields +`check/inconclusive`; otherwise the result is `check/passed`. Completed-inconclusive +always has a reason. + +For completed non-inconclusive execution, actual binding equals the corresponding +projection of the selected resolved binding. Performer is an adapter actor with +those same role/implementation/instance/principal/boundary/authority fields, +environment equals the request, and capability is the requested registered ID. +Recorded/computed provider, model, effort, prompt, +and skills equal the selected model binding; deterministic fields follow the +not-applicable rule. Recorded/computed tools are a subset of requested/offered tools; +an unavailable requested fact makes the result inconclusive. Snapshot has no +requested counterpart and may be unavailable. Failed, cancelled, or +completed-inconclusive execution may differ and preserves observed facts. Any other +difference forbids completed non-inconclusive. Metadata kind matches actual binding. + +Evidence IDs and kinds are each unique. Evidence context is the enclosing result. +Producer permits deterministic only; +verifier permits deterministic/behavioral/architecture with deterministic required; +reviewer permits independent-review only. Passing review requires observed reviewer +plus registered `core.review.change.v1`. Wrong performer/capability may carry only +non-passing request-allowed evidence. Prior evidence enters only through +`prior_evidence_refs` and cannot satisfy current evidence. + +### Pure validator and delivery + +`scripts/core-contract.sh` exposes exactly: ```text validate-document DOCUMENT validate-profile-set PROFILE RESOLVED_PROFILE MANIFEST... validate-stage-run REQUEST RESOLVED_PROFILE RESULT -validate-test-records INVENTORY RESULT EXPECTED_ACCEPTANCE_SCOPE ``` -The relational commands receive every document they compare. They recompute each -`document_ref` from the supplied canonical bytes. No command accepts a repository, -physical root, executable, command line, environment map, URL, or credential. - -Stable first-token errors are `E_USAGE`, `E_RUNTIME`, `E_PARSE`, `E_CANONICAL`, -`E_LIMIT`, `E_SHAPE`, `E_REF`, and `E_RELATION`. `E_RUNTIME` covers a missing or -wrong jq runtime and internal failure; candidate data cannot turn it into a valid -result. The wrapper never prints document content or local paths in an error. - -The implementation files are: - -- `core/v1/contracts.jq` — the sole executable contract source, at most 220 net lines; -- `scripts/core-contract.sh` — byte limits, canonical comparison, hashing, and the - four safe modes, at most 45 net lines; -- `scripts/test/portable-core-contract.test.sh` — table-driven positive and - adversarial tests with generated mutations, at most 100 net lines; -- small canonical fixtures only when table generation cannot express a byte case; -- README, `ci/required-files.txt`, and operator-applied CI wiring, together at most - 35 net lines. A fixture line consumes the same 400-line total budget. - -The line budget depends on one declarative field/capability registry consumed by -generic exact-object, tagged-union, set, and relation helpers. Tests build one valid -seven-document bundle and apply table-driven mutations; they do not duplicate one -fixture per rule. If the plan needs per-capability validators or repeated fixture -trees, the estimate has failed and the work returns to G2 before code. - -Implementation order is canonical parser and shared shapes; document shapes and -registry; relational modes and status/evidence rules; then adversarial tests, docs, -restore manifest, and CI. No second parser or copied registry is allowed. - -### Required adversarial coverage - -Tests reject malformed/noncanonical/oversized JSON; empty or multiple-root streams; -BOM, duplicate/escaped keys, -deep/wide/long data, floats and integer limits; unknown fields/versions/kinds; -invalid error-code or extension keys/values, or an extension used in place of a -required core field; unsafe IDs, refs, paths, -hashes, modes, source provenance, or floating refs; capability wildcards, role -mismatch, extra/missing arguments, command/argv/env/URL/network/secret/entrypoint -fields, permission drift, and model-role drift; shared protected-role bindings; -request/profile/result mismatch, including a changed request body with the same ID; -an actual performer/binding/environment/capability mismatch on a completed non-inconclusive -result; missing, extra, equal, or incorrectly derived mismatch records; evidence -that does not bind observed actual facts; passed independent-review evidence from a -non-reviewer, or non-reviewer R-kind evidence without the exact incident mismatch; -altered risk/selection/qualification/grant/gate refs; unexecuted -evidence or output; invalid status/outcome/evidence/time rules; replayed proof; -missing resolved bindings, wrong outcome family, free/unbound stale selectors, -mixed evidence precedence errors, and unoffered execution tools; -content-backed or wrong-repository execution snapshots; -unnamed or moved change-request base refs and tool configs without immutable refs; -topic-ref components beginning with `.`; -duplicate source objects with conflicting provenance and a 129-tool binding; -changed delta with replayed evidence, unrecorded performer authority/version drift, -missing -or wrong-purpose publisher-policy refs; invalid execution availability shapes, -source-claim refs, and model/deterministic combinations; -the absent generic label capability; empty or replaced inventories; -expected fields in results; dropped/extra/duplicate cases or assertions; mismatched -acceptance scope; and pass-looking prose, silence, empty, or degraded records that -omit required structured fields. Core tests do not judge whether a well-shaped -actual-fact or extension claim is truthful. - -Positive cases cover all seven kinds and all thirteen capabilities, including one -adapter implementation used through separate protected bindings, plus executed -failed, cancelled, and completed-inconclusive results that preserve each allowed -actual performer/binding/environment/capability mismatch, including a reviewer -performer-mismatch incident with non-passing -R-kind evidence. Core tests use -neutral logical IDs and do not special-case ystack. The unrelated Git target, -physical object attacks, fake processes, 2×2 substitution, timeouts, cleanup, and -external-target smoke belong to the two sibling initiatives. -Protected-context lookup and directive-bearing text bytes are required later -publisher/control-foundation tests, not observable core-validator cases. - -### Intent questions resolved - -1. **Smallest top-level set:** seven kinds. Five describe normal stage/profile - traffic. Separate inventory and observation records keep expectations outside the - runner result without creating a second contract family. Evidence and authority - records remain nested or referenced. -2. **Capabilities and permissions:** the thirteen-row closed registry and fifteen - permissions above are v1. Every argument field is named and typed. Anything that - cannot be expressed without a generic command, network request, credential, or - unbounded write is absent and requires a later major version. -3. **Validator boundary:** core checks canonical bytes, limits, shape, lexical refs, - closed registries, offer/request relations, protected-role separation, - request/result/evidence/status/time relations, and inventory/result linkage. It - does not read Git, authenticate, execute, authorize, or publish. -4. **Profile-resolution seam:** the resolver consumes canonical profile/manifests, - exact caller-supplied source and repository context, plus selection refs and - physical repository mappings. It produces canonical `resolved_profile` with - derived source-value claims. The sibling owns Git algorithms, object/mode/symlink - checks, replacement-object disabling, provenance derivation, and physical-path - safety. -5. **Adapter-test seam:** core owns the inventory/result envelopes and pure linkage - mode. The sibling owns external inventory selection, the fake-only launcher, - process and credential clearing, independent execution/revalidation, resolver - calls for Git facts, 2×2 comparison, unrelated-target fixtures, and proof that - observations came from execution rather than copied expectations. +No command accepts a repository path, physical root, executable, environment map, +URL, or credential. Success is exit 0 with empty stdout. Failure is nonzero and +starts stderr with `E_USAGE`, `E_RUNTIME`, `E_PARSE`, `E_CANONICAL`, `E_LIMIT`, +`E_SHAPE`, `E_REF`, or `E_RELATION`; input bytes and local paths are not echoed. + +Tests build one valid five-document bundle and run at least 60 table-driven +mutations covering canonical roots/limits, exact shapes/enums, root-tree and unsafe +paths, manifest/profile/source relations, the three capabilities and five permission +bounds, dormant/protected roles, request/result/status/time/output rules, +actual-fact incidents, model/tool availability, evidence replay/passing-role rules, +stale selectors, generic escapes, and all three commands. They do not read Git, +launch a process, or use a network. + +Implementation budget: + +| `contracts.jq` area | Lines | +|---|---:| +| canonical/limit/exact-object helpers | 35 | +| primitive/ref/document shapes | 65 | +| capability/permission/profile relations | 45 | +| request/result/status/evidence relations | 60 | +| **jq subtotal** | **205** | + +| Other area | Lines | +|---|---:| +| shell wrapper | 30 | +| table-driven tests and tiny fixtures | 135 | +| README, restore manifest, CI wiring | 30 | +| **Total** | **400** | + +The plan rejects code golf, long generated lines, copied registries, or a second +parser. If ordinary formatting exceeds 400 lines, return to G2. + +### Downstream handoffs and intent questions + +1. **Smallest records/refs:** five documents plus the shared refs above. Evidence is + nested; policy/test/telemetry bodies stay outside core. +2. **V1 capability/permission set:** three capabilities and five permissions. + Dormant roles preserve identity/separation but cannot execute. +3. **Validator boundary:** canonical shape, lexical refs, offer/request/provenance, + role separation, and request/result/status/evidence relations. No Git truth, + authentication, execution, policy truth, or external effect. +4. **Resolver seam:** consume canonical profile/manifests plus caller-owned exact + sources, selection, repository context, and physical repo map; emit canonical + resolved profile/source claims. Resolver alone checks Git truth. +5. **Adapter-test seam:** accepted inventory and observations are runner-owned exact + artifacts supplied as verifier input/proof. Adapter-facing traffic uses only the + five core docs; runner independently executes, revalidates every core document, + recomputes assertions/Git facts, and ignores adapter self-reported verdicts. Its + test-only format is not core schema or authority. + +The accepted adapter-test intent still promises a producer/forge 2×2 matrix. This +minimal core has no executable forge capability, so that G2 must wait for a separate +accepted forge contract or return to its own artifact gate under [#159](https://github.com/yihanzhu/ystack/issues/159). +Producer-only proof cannot be called the accepted 2×2. + +The operator's recorded [#172 scope-down ruling](https://github.com/yihanzhu/ystack/pull/172#issuecomment-5458286504) +also defers the intent's fixed publisher write. V1 preserves the protected publisher +binding but does not satisfy or activate that write. [#173](https://github.com/yihanzhu/ystack/issues/173) +tracks its one typed operation and permission, plus the other deferred role contracts, +before any publisher can run. ## Out of scope -- Git reads, repository-ID-to-path mappings, profile resolution, object existence, - file mode or symlink checks, replacement-object handling, and physical containment. -- Executable manifests, fake or real adapter launch, raw fixture reads, process - protocol, timeouts, environment/credential clearing, 2×2 execution, and the - unrelated-target smoke. -- Authentication, credentials, secrets, permission or qualification issuance, risk - policy evaluation, gate decisions, runtime sandbox/network enforcement, and - publisher execution, generic label projection, or any actual external write. -- Real/default/alternative adapter extraction, Codex native review, neutral-manager - implementation, profile activation, migration, packaging, install, or upgrade. -- Durable orchestration, retries, reconciliation, backpressure, kill switch, - deployment, rollback, incidents, production feedback, eval qualification, - telemetry aggregation, dashboards, or cost policy. -- Skill migration, bridge generation, YAML or Agent Skills conformance parsing, - bundled skill execution, non-Git canonical stores, or changes to artifact - frontmatter. -- Continuing PR #154 or implementation under `portable-control-plane-core`, closing - the parent roadmap item, or claiming that valid records prove portability, +- Core test-inventory/result kinds, fake runner, fixture/process execution, 2×2, + timeout/cleanup, and external-target smoke. +- Forge, CI, execution, identity, or publisher operations; GitHub/GitLab/native + transports; CR/comment/label/status; topic CAS; any external write. +- Git reads, physical repo mapping, object/mode/symlink/replacement checks, + config/tool provenance truth, authentication, credentials, grants, policy/gate + evaluation, qualification, sandbox/network enforcement, and kill switch. +- Retry/reconciliation/backpressure, deployment/rollback, production incidents, + telemetry/trace/usage/cost, packaging/install/migration, skill bridges, non-Git + stores, or live profile activation. +- Continuing #154, closing parent #153, or claiming these records prove portability, authorization, isolation, or correct execution. ## Areas of concern -1. **The old spec is not authority.** PR #154 closed unmerged and superseded. This - child restates only the record decisions needed now; it does not inherit the old - resolver or runner design. -2. **Pure validation has a hard honesty boundary.** A Git/content/actor/evidence ref - remains a claim. Downstream work must not advertise core exit 0 as proof of - existence, identity, authorization, or execution. -3. **Observed mismatch is incident data, not authority.** A valid failed or - inconclusive result may preserve the wrong actual actor/binding/environment or - capability. That - never authorizes it; control-foundation and orchestration work must stop the - workflow and route the incident to the correct recovery gate. -4. **Closed arguments are a G2 blocker.** If implementation needs an argument not - listed here, it returns to the artifact gate. It cannot add an opaque object, - command field, or namespaced execution escape. -5. **The runner cannot certify itself.** Inventory/result linkage prevents dropped - expectations but not fabricated observations. The sibling must independently run - and revalidate every case before a stage result can carry evidence. -6. **Declarative separation is not isolation.** Distinct IDs and refs do not create - separate credentials, sandboxes, or processes. Control-foundation work must prove - those runtime boundaries. -7. **Publisher records are proposals only.** Comment, status, branch, and - change-request shapes carry no permission to write and cannot project themselves - into gate authority. -8. **Implementation size may still expose excess scope.** The old 800-line exception - is gone. If the plan cannot stay within the normal review budget using one schema - and table-driven tests, reduce v1 or split again rather than weakening checks. -9. **CI is a constitution path.** The implementation plan must identify an - operator-driven edit or a `proposals/` handoff. G2 merge alone authorizes neither. -10. **No exceptional implementation is accepted here.** If jq limits, duplicate-key - handling, or portability require an architectural exception, return to the - accepted-artifact gate before code and satisfy the exceptional implementation - rule. Do not hide it in a parser workaround. -11. **The north-star marker is intentional.** The ystack-self entry keeps its - shipped-default marker and operator-history note for adopters. This user-directed - G2 adds no new proactive authorization and does not approve a live profile change. -12. **This is high-risk architecture.** G2 accepts design only. A later plan must be - reviewed under the repo's then-live risk gate; nobody may claim a pre-code plan - gate passed merely because this spec merged. -13. **Nothing activates on merge.** Contract/source changes do not regenerate - `/yshifu`, replace the manager persona, change adapters, or update an open - session. The operator remains the only merge authority. +1. **Dormant roles are not runnable adapters.** Identity fields preserve separation + only. Adding a capability/permission needs a new major. +2. **Refs and actual facts remain claims.** Core validity is not Git truth, identity, + evidence authenticity, qualification, or authority. +3. **Runtime controls remain mandatory.** Permission definitions state the allowed + envelope; control-foundation/adapters must enforce sandbox, credential, network, + and human-gate boundaries before real use. +4. **Forge 2×2 is unresolved by design.** The existing adapter-test intent must wait + or be separately rescoped; producer-only proof cannot be called its accepted 2×2. +5. **No implementation exception is accepted.** Strict parsing and the 400-line + bound cannot be met with a workaround; return to the artifact gate. +6. **CI is a constitution path.** Operator-driven work may edit it; unattended work + must use `proposals/` and wait for application. +7. **This is user-directed high-risk design.** G2 accepts only this spec, not a plan, + implementation, proactive work, or activation. +8. **Nothing changes live.** `/yshifu`, manager persona, adapters, and sessions stay + unchanged. Human merge remains the only merge path. From 666e29e6e3c7209917e8a2e36237e07dd6824025 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 19:12:04 -0400 Subject: [PATCH 08/15] spec: close four core contract gaps --- work/portable-core-contracts/spec.md | 122 ++++++++++++++++++++------- 1 file changed, 93 insertions(+), 29 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 1afbb88..0d3f617 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -24,7 +24,8 @@ authority, or perform an external write. evidence write, and conditional model invoke have the exact resource/action bounds in Design. None grants shell, arbitrary command, environment, general network, credential, generic file/Git-ref write, approval, merge, bypass, deploy, or human - impersonation. + impersonation. Exact instruction bytes arrive by value through the bounded launch + seam in Design; the adapter gets no content-store read authority. - **R4 — strict canonical JSON.** A document is exactly one UTF-8 JSON value whose bytes equal the pinned jq 1.6 single-root canonicalizer plus one line feed. Reject empty/multi-root streams, BOM, invalid UTF-8, duplicate keys, alternate @@ -50,7 +51,8 @@ authority, or perform an external write. - **R9 — one closed operation per request.** A request binds one runnable resolved binding, one capability, its exact permissions/arguments, target and inputs, risk/gate claims, environment, finish condition, verification instructions, - required evidence kinds, and time. Retry sequence/delivery are later concerns. + required evidence kinds, and time. The launcher derives one exact instruction set + from those refs. Retry sequence and message delivery are later concerns. - **R10 — total result truth.** A result records one terminal status and attempt, observed execution facts when work ran, outputs, diagnostics, and evidence. Completed conclusive execution equals the request. Failed, cancelled, and @@ -87,9 +89,11 @@ Objects are exact; unlisted fields fail. `T?` is omitted-or-present, never `null Core `ID` matches `^[a-z0-9][a-z0-9._:-]{0,127}$`. `Int` is `0..2147483647`. `SHA256` is 64 lowercase hex. `Version` is an exact ID. -`ShortText` is 1–1,024 decoded UTF-8 bytes and is never authority. `Time` is a -real UTC second-level `YYYY-MM-DDTHH:MM:SSZ`. `MediaType` is a lowercase -`type/subtype` token at most 127 characters. `GitOID` is 40 lowercase hex for +`ShortText` is 1–1,024 decoded UTF-8 bytes and is never authority. `RawBytes` is an +uninterpreted byte string used only inside the non-persisted transport frame. `Time` +is a real UTC second-level `YYYY-MM-DDTHH:MM:SSZ`. `MediaType` is a lowercase +`type/subtype` token at most 127 characters; `PatchMediaType` is exactly +`text/x-diff`. `GitOID` is 40 lowercase hex for SHA-1 or 64 for SHA-256. `git-key` is the lexical tuple `(repository_id,hash_algorithm,commit_id,location kind/value,object_type,object_id,mode)`. `selector-tuple` is `(kind,input_id-or-scope_sha256-or-empty)`. Both are compared @@ -138,8 +142,9 @@ This union is named `Fact`. | `actor_ref` | `{role:ActorRole,implementation_id:ID,implementation_version:Version,adapter_instance_id:ID,principal_id:ID,execution_boundary_id:ID,authority_ref?:scope_ref(authority)}` | | `environment_ref` | `{environment_id:ID,fingerprint_sha256:SHA256}` | | `tool_ref` | `{tool_id:ID,tool_version:Version,package_ref:git_object_ref,config_ref:present}` | -| `change_ref` | `{repository_id:ID,base:present,head:git_revision_ref,delta_ref:content_ref}`; revisions match repository | -| `source_value_ref` | `{source:git_object_ref,value_format:"raw-bytes"|"canonical-json",value_sha256:SHA256}` | +| `git_patch_ref` | `content_ref` with `media_type=PatchMediaType` | +| `change_ref` | `{repository_id:ID,base:present,head:git_revision_ref,delta_ref:git_patch_ref}`; revisions match repository | +| `source_value_ref` | `{source:git_object_ref,value_format:"raw-bytes"|"canonical-json",value_sha256:SHA256}`; `canonical-json` requires `source.object_type="blob"` | `RepoPath` is a non-empty repo-relative POSIX path with no empty, `.`, `..`, backslash, NUL, or control segment. Root tree is `git_location:{kind:"root"}`, never @@ -149,8 +154,15 @@ to the resolver. Scope purposes are `selection`, `repository-context`, `qualification`, `grant`, `policy`, `authority`, `gate-requirement`, `gate-decision`, `config-contract`, `output-contract`, `allowed-delta`, `verification-plan`, `review-policy`, -`finish-condition`, and `verification-instructions`. Scope refs carry identity only; -v1 never forwards their bytes as commands or permissions. +`finish-condition`, and `verification-instructions`. Scope refs carry identity only. +Core never interprets their bytes as authority or lets them change an operation or +permission. Only the exact operation instruction refs use the launch seam below. + +`delivered_scope(P)` is exactly `{ref:scope_ref(P),input_id:ID}`. Its subject must be +`{type:"artifact",value:{type:"content",value:content_ref}}`. In a request, its +`input_id` selects exactly one named input whose value equals that complete subject. +The decision-record ref remains acceptance provenance; it is never the instruction +payload. ### Manifest, profile, and resolved profile @@ -219,8 +231,9 @@ grant, qualification, gate, trust, or activation field. ``` Profile/manifest sources use `canonical-json` and their value digests equal the -document refs. Each resolved `binding` equals its profile binding, and its manifest -ref selects exactly one supplied manifest. Package/config/prompt/skill/tool source +full canonical document bytes, including the final line feed, in their refs. Each +resolved `binding` equals its profile binding, and its manifest ref selects exactly +one supplied manifest. Package/config/prompt/skill/tool source objects and presence equal their corresponding binding or tool refs; skill/tool source sets are one-to-one with those refs. A resolved implementation ID/version equals its manifest document ID/adapter version. Across the resolved profile, one @@ -237,9 +250,9 @@ Git/repository/object/provenance truth. | Capability | Exact arguments | Exact permissions | Outcome / evidence | |---|---|---|---| -| `core.harness.produce.v1` | `{artifact_kind:"plan"|"structured-artifact",output_contract_ref:scope_ref(output-contract)}` or `{artifact_kind:"git-patch",allowed_delta_ref:scope_ref(allowed-delta)}` | target.read + scratch.write + evidence.write; model.invoke iff model | change; deterministic only | -| `core.verify.run.v1` | `{candidate_input_id:ID,verification_plan_ref:scope_ref(verification-plan),network_mode:"deny"}`; input is target Git tree | target.read + candidate.execute + evidence.write | check; deterministic required, behavioral/architecture optional | -| `core.review.change.v1` | `{change_ref:change_ref,review_policy_ref:scope_ref(review-policy)}` | target.read + evidence.write; model.invoke iff model | check; independent-review only | +| `core.harness.produce.v1` | `{artifact_kind:"plan"|"structured-artifact",output_contract:delivered_scope(output-contract)}` or `{artifact_kind:"git-patch",allowed_delta:delivered_scope(allowed-delta)}` | target.read + scratch.write + evidence.write; model.invoke iff model | change; deterministic only | +| `core.verify.run.v1` | `{candidate_input_id:ID,verification_plan:delivered_scope(verification-plan),network_mode:"deny"}`; candidate input is target Git tree | target.read + candidate.execute + evidence.write | check; deterministic required, behavioral/architecture optional | +| `core.review.change.v1` | `{change_ref:change_ref,review_policy:delivered_scope(review-policy)}` | target.read + evidence.write; model.invoke iff model | check; independent-review only | Permission IDs and full meaning: @@ -265,6 +278,51 @@ generic filesystem/network, credential/secret, Git-ref write, CR/comment/label/s force/delete, approval/merge/bypass, policy activation, deploy, or human impersonation. +### Bounded instruction delivery + +`operation_instructions(request)` is the three `delivered_scope` values containing +the request's finish condition and verification instructions plus one capability +value: producer output contract or allowed delta, verifier verification plan, or +reviewer review policy. Their input IDs are distinct and differ from the verifier's +candidate input ID. Policy, selection, repository context, qualification, grant, +gate, authority, config contract, and candidate-selected refs are excluded. + +The non-persisted v1 transport frame is +`{version:1,request_ref:document_ref(stage_request),items:set +(purpose,3..3)}`. An item is `{purpose:ScopePurpose,scope_ref:scope_ref, +input_id:ID,content_ref:content_ref,byte_length:Int,bytes:RawBytes}`. Its purpose, +complete scope ref, input ID, and content ref equal one derived `delivered_scope`, its +named input, and that scope's content-artifact subject. It never uses +`decision_record_ref` as payload. The frame is an invocation argument, not a sixth +core document, artifact, capability, permission, or authority record. + +Before a real adapter starts, the caller-controlled launcher pushes exactly this +frame. It first checks each raw value is at most 1,048,576 bytes and the total is at +most 3,145,728 bytes. It then hashes the unchanged raw bytes and requires the exact +content ID, media type, SHA-256, byte length, scope ref, input ID, request ref, and +purpose above. Hashing happens before decoding; no newline, Unicode, escape, or +whitespace normalization is allowed. Instruction media type is exactly `text/plain` +or `application/json`. Text is UTF-8 without BOM or NUL. JSON uses the same +single-root canonical byte form and size/depth/member/string/integer limits as +R4/R5, but is not a core envelope. Compression, multipart, missing, extra, duplicate, +oversized, cross-request, or mismatched items stop before adapter execution. + +The launcher passes the verified in-memory buffers once. It passes no content-store +handle, path, URL, lookup/list operation, credential, or reusable read capability, +and never re-fetches after checking. Reading these already-delivered call arguments +is not an external-read permission. Their contents may only narrow the selected +capability inside the request's fixed target, arguments, permissions, and evidence +rules. They cannot add authority, tools, network, another input, or executable +shell/argv/env meaning; a conflict with typed fields fails closed. Verifier +instruction buffers never enter the candidate sandbox. + +The pure validator checks the request's three exact purposes plus their subject, +input, media-type, and distinct-ID relations. It never accepts the transport frame or +raw bytes. The later launcher/control-foundation boundary enforces the frame before +real use and tests missing, extra, duplicate, oversized, wrong-digest, +cross-purpose/request, re-fetch, candidate-leak, and attempted-lookup cases. Digest +equality proves byte identity only, not acceptance, safety, or execution authority. + ### Stage request `named_input` is `{input_id:ID,value:input_ref}`. `risk_claim` is @@ -290,8 +348,8 @@ selected only by the capability table. qualification_ref?:scope_ref(qualification),grant_ref?:scope_ref(grant), gate_decision_refs:set(scope_sha256,0..256), environment_ref:environment_ref,operation:operation, - finish_condition_ref:scope_ref(finish-condition), - verification_instruction_ref:scope_ref(verification-instructions), + finish_condition:delivered_scope(finish-condition), + verification_instruction:delivered_scope(verification-instructions), required_evidence_kinds:set<"deterministic"|"behavioral"|"architecture"| "independent-review">(value,1..3),requested_at:Time} ``` @@ -360,6 +418,8 @@ or `cancelled`. ```text {selector:{kind:"target"},observed:present} +{selector:{kind:"source"},observed:present} +{selector:{kind:"base"},observed:present} {selector:{kind:"resolved-profile"},observed:present} {selector:{kind:"qualification"},observed:present} {selector:{kind:"environment"},observed:present} @@ -369,12 +429,14 @@ or `cancelled`. ``` Expected is derived from the selected request field/set member and must differ from -observed in presence or value. A present target observation uses the target -repository. A present resolved-profile observation keeps the expected kind/ID; a -present environment keeps the expected environment ID. Input ID selects the exact -request input. Qualification is the request's sole optional qualification. A gate -selector names one exact requested decision by its scope digest. No other selector -or identity rule is inferred. +observed in presence or canonical value. Target, source, and base each select their +sole request slot. A present target/base or Git-backed source observation uses the +target repository; a present base keeps the expected hash algorithm when both are +present. A present resolved-profile observation keeps the expected kind/ID; a present +environment keeps the expected environment ID. Input ID selects the exact request +input. Qualification is the request's sole optional qualification. A gate selector +names one exact requested decision by its scope digest. Equal values, wrong +repositories/types, or duplicate selectors fail. No other identity rule is inferred. `stage_result.body` is: @@ -383,7 +445,7 @@ or identity rule is inferred. attempt_id:ID,attempt_number:Int,reported_by:actor_ref,status:TerminalStatus, outcome?:outcome,reason?:reason, stale_observations?:set(selector-tuple,1..256), - outputs:set(output_id,0..256),delta_ref?:content_ref, + outputs:set(output_id,0..256),delta_ref?:git_patch_ref, diagnostics:set(content_id,0..256),execution?:execution, evidence:set(evidence_id,0..256),started_at?:Time,finished_at?:Time, recorded_at:Time} @@ -412,8 +474,9 @@ number is at least 1. Time order is For completed producer results, any non-passing evidence yields `change/inconclusive` with no output/delta. Otherwise one output yields -`change/changed`; `delta_ref` is present and equals `output.ref` only for `git-patch`, -and is absent for the other artifact kinds. Empty output/delta yields +`change/changed`; for `git-patch`, `output.ref` is a `git_patch_ref` and `delta_ref` +is present and equals it. Delta is absent for the other artifact kinds. Empty +output/delta yields `change/no-change`. For completed verifier/reviewer results, output/delta are empty: any failed evidence yields `check/failed`; otherwise any inconclusive evidence yields @@ -457,11 +520,12 @@ starts stderr with `E_USAGE`, `E_RUNTIME`, `E_PARSE`, `E_CANONICAL`, `E_LIMIT`, Tests build one valid five-document bundle and run at least 60 table-driven mutations covering canonical roots/limits, exact shapes/enums, root-tree and unsafe -paths, manifest/profile/source relations, the three capabilities and five permission -bounds, dormant/protected roles, request/result/status/time/output rules, +paths, canonical-JSON tree rejection, manifest/profile/source relations, the three +capabilities and five permission bounds, instruction purpose/subject/input closure, +dormant/protected roles, request/result/status/time/output and patch-media rules, actual-fact incidents, model/tool availability, evidence replay/passing-role rules, -stale selectors, generic escapes, and all three commands. They do not read Git, -launch a process, or use a network. +source/base and other stale selectors, generic escapes, and all three commands. They +do not read Git, launch a process, or use a network. Implementation budget: From 0488807ee288f6cf103f0404864008e1e3550cc0 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 19:33:34 -0400 Subject: [PATCH 09/15] spec: close three relation gaps --- work/portable-core-contracts/spec.md | 56 ++++++++++++++++++---------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 0d3f617..064f5bd 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -196,9 +196,9 @@ Capabilities are exactly `core.harness.produce.v1`, `core.verify.run.v1`, and `model_request` is `{provider_id:ID,model_id:ID,effort_id:ID}`. Producer/reviewer may be model-backed. Every model binding requires model request and prompt; every -deterministic binding forbids both. Verifier and all dormant roles are deterministic. -Producer, verifier, reviewer, and publisher bindings occur exactly once. Other roles -are optional and unique by role. +deterministic binding forbids both and requires an empty `skill_refs` set. Verifier +and all dormant roles are deterministic. Producer, verifier, reviewer, and publisher +bindings occur exactly once. Other roles are optional and unique by role. Producer requests only the producer capability; verifier only verifier; reviewer only reviewer. Dormant roles request zero capabilities/permissions and cannot be @@ -233,12 +233,26 @@ grant, qualification, gate, trust, or activation field. Profile/manifest sources use `canonical-json` and their value digests equal the full canonical document bytes, including the final line feed, in their refs. Each resolved `binding` equals its profile binding, and its manifest ref selects exactly -one supplied manifest. Package/config/prompt/skill/tool source -objects and presence equal their corresponding binding or tool refs; skill/tool -source sets are one-to-one with those refs. A resolved implementation ID/version -equals its manifest document ID/adapter version. Across the resolved profile, one -exact source object has only one format/digest claim; a set cannot contain the same -`git-key` twice. +one supplied manifest. For each profile binding `b`, let `m` be that manifest and +`rb` the one resolved binding with the same binding ID. Validation enforces: + +```text +rb.binding == b +b.package_ref == m.body.package_ref +rb.package_source.source == b.package_ref +b.requested_tools is a subset of m.body.offered_tools by full tool_ref equality +IDs(rb.tool_sources) == IDs(b.requested_tools) +each resolved tool package/config source and presence equals its requested tool +b.config_ref present => m.body.config_contract_ref present +rb.config_source presence/source == b.config_ref presence/object +``` + +Prompt and skill source presence/object sets likewise equal their binding refs, so a +deterministic binding has no resolved skill sources. A resolved implementation +ID/version equals its manifest document ID/adapter version. Across the resolved +profile, one exact source object has only one format/digest claim; a set cannot +contain the same `git-key` twice. Missing/extra tools, a matching tool ID with any +different version/package/config, or config without a manifest contract fails. `validate-profile-set` recomputes supplied document refs and enforces exact manifest set, binding-ID set, role/execution offer, package/config/tool relations, capability @@ -359,12 +373,12 @@ resolved profile. The binding is non-dormant, owns the capability, and requests exact effective permissions; this is compatibility, not activation or grant. Every Git ref in target revision, base, source, inputs, or reviewer change uses `target_repository_id`; resolved-profile sources are not target inputs. Verifier -candidate ID selects exactly one input whose value is a Git tree. Reviewer change -head equals the present target revision and change base equals request base. An -absent target revision is allowed only for bootstrap producer work. Required -evidence kinds equal the capability rule: producer and reviewer use their one named -kind; verifier includes deterministic and may also request behavioral and -architecture. +candidate ID selects exactly one Git-tree input, and that tree's full revision equals +the present target revision. Reviewer change head equals the present target revision +and change base equals request base. An absent target revision is allowed only for +bootstrap producer work. Required evidence kinds equal the capability rule: producer +and reviewer use their one named kind; verifier includes deterministic and may also +request behavioral and architecture. ### Stage result and evidence @@ -521,11 +535,13 @@ starts stderr with `E_USAGE`, `E_RUNTIME`, `E_PARSE`, `E_CANONICAL`, `E_LIMIT`, Tests build one valid five-document bundle and run at least 60 table-driven mutations covering canonical roots/limits, exact shapes/enums, root-tree and unsafe paths, canonical-JSON tree rejection, manifest/profile/source relations, the three -capabilities and five permission bounds, instruction purpose/subject/input closure, -dormant/protected roles, request/result/status/time/output and patch-media rules, -actual-fact incidents, model/tool availability, evidence replay/passing-role rules, -source/base and other stale selectors, generic escapes, and all three commands. They -do not read Git, launch a process, or use a network. +capabilities and five permission bounds, full package/tool/config equality, +instruction purpose/subject/input closure, dormant/protected roles, empty +deterministic skills, verifier target-revision equality, +request/result/status/time/output and patch-media rules, actual-fact incidents, +model/tool availability, evidence replay/passing-role rules, source/base and other +stale selectors, generic escapes, and all three commands. They do not read Git, +launch a process, or use a network. Implementation budget: From 1b93923374fa39e28f76457769769eba8529f8b2 Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 29 Aug 2026 16:25:14 -0400 Subject: [PATCH 10/15] spec: define reviewable portable core children --- work/portable-core-contracts/spec.md | 576 ++++++++++++++++++++++----- 1 file changed, 483 insertions(+), 93 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index f5b2186..580210b 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -1,6 +1,7 @@ --- -intent-blob: 3ed8bb434c096ec126d680019a9491ab8a113e31 -drafted: 2026-08-28 +intent-blob: f77fc1fdd8f7af228e7f211740901b265fc545ae +risk: high +drafted: 2026-08-29 --- # Spec: portable core contracts @@ -67,28 +68,245 @@ authority, or perform an external write. snapshot, effort, prompt, skills, and tools are recorded/computed/unavailable. Missing facts carry a reason and are never copied from requested profile values. Trace, usage, and cost move to telemetry. -- **R13 — pure validation.** One schema source validates canonical bytes, exact - shapes, registries, manifest/profile/resolution relations, and - request/result/status/evidence rules. It never resolves physical repositories, - launches a process, reads a raw fixture, or evaluates policy. -- **R14 — reviewable, evidence-backed delivery.** The plan estimates normally - formatted size and names cohesive review units for one jq source, one shell front - door, table-driven tests, docs, restore entries, and operator-owned CI wiring. Line - count is a reviewability signal, not a pass/fail gate. Complete behavior, complete - tests, CI, independent review, and the safety boundaries remain hard requirements. - Implementation remains one reviewable concern on the deterministic - `ystack/impl/portable-core-contracts` branch and PR. The operator accepts the - current estimate as an explicit exception to the repository's soft review-size - guide; line count alone does not require a split. The plan groups the one PR into - clear commits and review sections. Any actual concern or scope expansion returns - to the artifact gate. Nothing public or live activates before the full contract - and integration proof are accepted. +- **R13 — one contract package, several private owners.** V1 is one versioned + package with the semantic identity `core.contracts.v1`. Shared types and policy + tables, profile rules, request rules, result-fact rules, and result-truth rules + each have one private owner. A later owner may import an earlier one; it may not + copy a document kind, role, capability, permission, evidence, outcome, shape, or + acceptance rule into a second home. Product modules never resolve physical + repositories, launch a candidate process, depend on test fixtures, or evaluate + policy. +- **R14 — seven ordered child initiatives.** Delivery uses exactly + `portable-core-schema`, `portable-core-ingress`, + `portable-core-profile-graph`, `portable-core-stage-request`, + `portable-core-result-facts`, `portable-core-result-truth`, and + `portable-core-assembly`. Every child has its own intake, intent, spec with + `risk: high`, high-risk plan, implementation PR, real CI proof, independent + review, and human merge. Every child artifact and plan PR tracks its child intake. + Each child implementation closes that intake. With respect to parent #155, the + first six implementations use `Tracks #155`; assembly also uses `Closes #155`. - **R15 — no live change.** This work does not activate a profile, extract a real adapter, regenerate `/yshifu`, alter an open session, or enable autonomous writes. The operator remains the only merge authority. +- **R16 — partial packages stay private.** The first six children add only private + package members, test drivers, fixtures, and their exact CI proof. They do not add + or document a public validator command, install path, profile selection, or live + caller. Only `portable-core-assembly` may add the public shell command, root + dispatcher, full-package user documentation, final restore instructions, and + three public command forms. Every child immediately lists its own restore-critical + files in `ci/required-files.txt`; assembly owns only the final package/user view. +- **R17 — code loading is fixed.** The assembly wrapper resolves its own physical + repository root and loads one literal root program plus one literal private module + directory. It passes jq 1.6 exactly one `-L` directory, which disables jq's builtin + module search list. Product jq files may use only exact statements of the form + `import "NAME" as NAME;`, where `NAME` is one of the five accepted module names. + `include`, import metadata (including `search`), JSON imports, and names containing + `/`, `.`, `..`, `~`, `$ORIGIN`, or backslash are forbidden. Caller arguments, cwd, + `HOME`, module-search environment, config, document content, URLs, and ambient jq + modules cannot select a jq module or shell library. The resolved repository root + and every package-path directory component are real directories reached without a + symlink. `scripts/core-contract.sh`, `scripts/lib/core-ingress.sh`, + `core/v1/contracts.jq`, and every fixed module are regular non-symlink files inside + that same physical repository. Any mismatch stops as `E_RUNTIME`. These + restrictions close both search mechanisms documented by the + [jq 1.6 module rules](https://jqlang.org/manual/v1.6/#modules): the default list + selected by `-L` and the optional `search` import metadata. Host executables found + through `PATH` are an operator-controlled runtime dependency, not part of this + module-search guarantee; CI separately pins the exact jq 1.6 release asset. +- **R18 — every document receives its complete self-check.** Every command applies + the same ordered per-document pipeline to every supplied document: parsed limits, + shape, ref syntax, and all self-contained relations owned by that document's + module. A command may then add only its named cross-document checks. No command may + choose a smaller version of a document's self-check. +- **R19 — assembly routes; it does not redefine rules.** The root dispatcher owns + command routing, fixed error order, and composition only. It contains no profile, + request, result, capability, permission, evidence, or outcome rule. A defect in a + private owner returns to that child's artifact gate; assembly never patches it by + copying the rule. +- **R20 — proof is complete by rule, route, and two frozen ledgers.** Every owned + rule has a stable `.` rule ID listed in its child spec, direct valid + and invalid cases, every required command-to-rule route, and a forced-route test. + Tests keep expected verdicts as literal data and never use product policy as their + oracle. The parent plan freezes two inventories from PR #183 head + `ab4a7082f02e67b5748c5c54b9214f37d222f53f`: + 31 discrete review-finding rows from comments 5463604326, 5463851247, + 5464015820, and 5464192510, plus the 279 assertion results emitted in execution + order by `scripts/test/core-contract.test.sh` at that head. Finding IDs are + `review-rN-fNN`; assertion IDs are `legacy-test-001` through `legacy-test-279` + with their original output label. Repeated findings remain separate rows but may + point to the same new owner/rule. Every finding maps to one owner and closing + rule/test or a reasoned superseding rule. Every assertion maps to one owner; + `ported` and `replaced-by` name exact new rule/test IDs, while + `invalid-old-positive` gives a reason. The required denominators are 31/31 and + 279/279; neither count proves semantic completeness by itself. +- **R21 — PR #183 is evidence, not implementation.** No child branches from, merges, + or wholesale cherry-picks PR #183. Code and tests may move only after receiving one + owner and fresh proof on the child's accepted base. The old commit history and four + review rounds are defect evidence, not acceptance evidence. The #180 bridge ended + when G1 changed the intent blob and how the work is split and delivered; it + authorizes no child, branch, PR, or resume. +- **R22 — cross-child pins fail closed.** Each child spec records its direct upstream + accepted spec blobs. Each downstream high-risk plan waits for and records all + required upstream G3 tuples. A tuple is exactly + `{merge_commit, exports:[{path,mode,type,oid}]}` using that child's closed product + export list in Design. Before downstream G2 review, compare only the direct spec + blobs with current main. Before downstream plan review, code, CI review, and final + review, compare those blobs again and also require each G3 merge commit to be an + ancestor of current main and every export tuple to equal the current-main Git + entry. Any move or mismatch marks the downstream work stale and returns it through + its own G2 and high-risk plan gates; code already written is preserved, but its + review evidence is stale. Assembly performs the G3 check for all six upstream + children. Shared CI, restore manifest, activation-guard, test-harness, fixture, + and documentation paths are not product exports; every downstream exact-head/base + review instead reruns and binds their current versions. +- **R23 — private state is enforced until assembly.** The schema child establishes a + deterministic activation guard. Every first-six child runs it in CI and proves + there is no public command/root dispatcher, install/export/profile wiring, user + documentation claiming a complete package, or non-test caller of private modules + or ingress. Only fixed private test drivers are allowed. Assembly G3 deliberately + replaces that expectation with a guard that permits the one public wrapper/root + while still proving no live profile, manager, template, or install path calls it. ## Design +### Package owners and child order + +`core.contracts.v1` names the semantic contract package. It is not a trust claim, +Git identity, grant, or authority. Exact source provenance remains a caller-owned +Git tree claim outside this validator. + +The accepted dependency graph is: + +```text +portable-core-schema + ├─> portable-core-ingress + └─> portable-core-profile-graph + └─> portable-core-stage-request + ├─> portable-core-result-facts + └─> portable-core-result-truth +portable-core-result-facts ────────────┘ + +all six private children ──> portable-core-assembly +``` + +The diagram shows scheduling order, not every source import. The table below is +the exact direct-dependency list and its full upstream G3 closure. Children on the +same level may proceed in parallel after their common prerequisites merge. + +Each child spec frontmatter adds `upstream-spec-blobs`, an exact direct-dependency +map of `slug → git-blob` (`{}` when none). Each child plan adds `upstream-g3`, a +full-closure map of `slug → {merge_commit,exports:[{path,mode,type,oid}]}`. G2 review +compares the first map with the current-main child spec files. Plan review, code, CI +review, and final review also compare the second map under R22. No issue comment or +branch state substitutes for those exact identities. + +| Child | One private responsibility | Direct dependencies | All required upstream G3 | Expected net new lines: product + owned proof | +|---|---|---|---|---:| +| `portable-core-schema` | parsed depth/member/string/integer limits; primitives; shared refs; envelopes; document-kind registry; one declarative role/capability/permission/evidence policy table | none | none | 360–460 | +| `portable-core-ingress` | raw-byte limit and bounded snapshot, jq 1.6 canonical bytes, hashes, private temp I/O, sanitized errors | schema | schema | 230–320 | +| `portable-core-profile-graph` | manifest/profile/resolved-profile exact body shapes, self relations, and supplied-document graph relations | schema | schema | 360–480 | +| `portable-core-stage-request` | request exact body shape, capability arguments, permissions, target/input/instruction/evidence closure, request-to-binding relation | schema, profile graph | schema, profile graph | 300–400 | +| `portable-core-result-facts` | actual-binding/execution/fact shapes plus comparison with the request-owned expected execution projection | schema, profile graph, stage request | schema, profile graph, stage request | 280–380 | +| `portable-core-result-truth` | stage-result exact body shape, six-status presence, stale observations, evidence closure, outcome reduction, outputs/delta/time | schema, profile graph, stage request, result facts | schema, profile graph, stage request, result facts | 380–500 | +| `portable-core-assembly` | fixed dispatcher, public wrapper, command routing, full-package proof, aggregate CI, user docs, and final restore view | all six | all six | 300–420 | +| **Package total** | | | | **about 2,210–2,960** | + +These are normally formatted net-new-line ranges relative to each child's accepted +base, including product code and owned proof. They are planning inputs only. Every +child G2/plan independently records `review_size: standard|accepted-exception`; it +does not inherit an exception from this parent. The ranges are not permission to add +a second responsibility, compress code, copy policy, or reduce negative tests. A +child that cannot stay independently reviewable returns to its own artifact gate +before code. + +The schema policy table is the only source for literal role/capability/permission/ +evidence sets and mappings. Profile, request, and result owners do not redefine those +constants; they alone own the rules that apply the shared constants to their exact +document shapes and cross-document relations. + +This spec fixes the private package layout: + +```text +core/v1/modules/schema.jq +core/v1/modules/profile_graph.jq +core/v1/modules/stage_request.jq +core/v1/modules/result_facts.jq +core/v1/modules/result_truth.jq +scripts/lib/core-ingress.sh +``` + +Allowed jq imports are exact: + +| Importer | Allowed module names | +|---|---| +| `schema.jq` | none | +| `profile_graph.jq` | `schema` | +| `stage_request.jq` | `schema`, `profile_graph` | +| `result_facts.jq` | `schema`, `profile_graph`, `stage_request` | +| `result_truth.jq` | `schema`, `profile_graph`, `stage_request`, `result_facts` | +| assembly `contracts.jq` | all five, once each | + +CI statically rejects every other import/include/module directive or metadata form +and behaviorally proves fake search roots cannot change the loaded program. + +Product export ownership is also closed and disjoint: + +| Child | Immutable product exports used by downstream pins | +|---|---| +| `portable-core-schema` | `core/v1/modules/schema.jq` | +| `portable-core-ingress` | `scripts/lib/core-ingress.sh` | +| `portable-core-profile-graph` | `core/v1/modules/profile_graph.jq` | +| `portable-core-stage-request` | `core/v1/modules/stage_request.jq` | +| `portable-core-result-facts` | `core/v1/modules/result_facts.jq` | +| `portable-core-result-truth` | `core/v1/modules/result_truth.jq` | +| `portable-core-assembly` | `core/v1/contracts.jq`, `scripts/core-contract.sh` | + +No other path may appear in an `upstream-g3` export tuple. A later child never edits +an earlier child's product export. A needed change returns through that earlier +child's artifact and plan gates, then refreshes every affected downstream pin. +Shared proof and aggregate paths may change only within accepted child scope and are +revalidated on the current head/base; they never masquerade as another child's +immutable product export. + +The assembly child alone adds: + +```text +core/v1/contracts.jq +scripts/core-contract.sh +``` + +`contracts.jq` uses literal, namespaced imports. `core-contract.sh` resolves its +own physical repository root, sources only the fixed ingress library, and runs jq +with the literal module root `core/v1/modules`. The public command accepts no code, +module, schema, search-path, or test-hook argument. + +### Parent plan, child intake, and the failed attempt + +Merging this G2 amendment accepts the package design and `risk: high`; it does not +approve a child intake, plan, or code. A new parent high-risk plan-only PR then pins +this spec and records the seven exact child issue drafts, dependency order, review +ranges, PR #183 migration ledger ownership, and PR #183 disposition. That parent +plan creates no `ready` state and authorizes no new +`ystack/impl/portable-core-contracts` work. + +After the parent plan merges, each child issue receives its own exact-title/body +user-directed acceptance record and complete intent → spec-with-risk → high-risk +plan → implementation chain. Child artifact and plan PRs track their child issue +and may also track #155. Each child implementation closes its own issue. With +respect to parent #155, the first six implementation PRs use `Tracks #155`; the +assembly implementation uses `Closes #155`. + +PR #183 remains frozen in its exact paused state after the final review round while +G2 and the parent plan are under review: head +`ab4a7082f02e67b5748c5c54b9214f37d222f53f`, reviewed base +`14988a8a5392e888ff1aaee4c48afa5024bee003`, `round-3 + needs-human`, clean +worktree, and open PR. Any unexplained move stops the amendment process. After G2 and +the parent plan merge and all seven child issue numbers exist, the operator records +the replacement links and closes #183 as superseded before any child code starts. +It is never merged. Its exact head remains a read-only source snapshot until +assembly G3, but no old CI, review, branch, plan, bridge, or commit is reused as +acceptance evidence. + ### Canonical notation and primitives Objects are exact; unlisted fields fail. `T?` is omitted-or-present, never `null`. @@ -154,10 +372,10 @@ This union is named `Fact`. | `change_ref` | `{repository_id:ID,base:present,head:git_revision_ref,delta_ref:git_patch_ref}`; revisions match repository | | `source_value_ref` | `{source:git_object_ref,value_format:"raw-bytes"|"canonical-json",value_sha256:SHA256}`; `canonical-json` requires `source.object_type="blob"` | -`RepoPath` is a non-empty repo-relative POSIX path with no empty, `.`, `..`, -backslash, NUL, or control segment. Root tree is `git_location:{kind:"root"}`, never -an empty path. Symlink/gitlink modes are not representable; physical checks belong -to the resolver. +`RepoPath` is a non-empty repo-relative POSIX path with no empty, `.`, `..`, or +backslash segment and no Unicode control code U+0000–U+001F or U+007F–U+009F. Root +tree is `git_location:{kind:"root"}`, never an empty path. Symlink/gitlink modes are +not representable; physical checks belong to the resolver. Scope purposes are `selection`, `repository-context`, `qualification`, `grant`, `policy`, `authority`, `gate-requirement`, `gate-decision`, `config-contract`, @@ -262,6 +480,19 @@ profile, one exact source object has only one format/digest claim; a set cannot contain the same `git-key` twice. Missing/extra tools, a matching tool ID with any different version/package/config, or config without a manifest contract fails. +`portable-core-profile-graph` gives these rules two named checks: + +- `resolved_profile_self_ok` needs no supplied profile or manifest. It checks the + embedded binding's capability/permission closure, protected-role separation, + profile-source digest claim, each manifest-source digest against its embedded + `manifest_ref.sha256`, each `adapter_implementation.id` against that manifest ref + ID, package/config/prompt/skill/tool source projections, and one claim per exact + source object. Every command that receives a resolved profile runs this full check. +- `profile_set_graph_ok` owns only relations that need the separately supplied + profile or manifests: profile-to-resolved equality, exact supplied manifest set + and digests, offered roles/execution/capabilities/permissions/tools, adapter + implementation version, package equality, and config-contract presence. + `validate-profile-set` recomputes supplied document refs and enforces exact manifest set, binding-ID set, role/execution offer, package/config/tool relations, capability subsets, permission unions, source relations, and pairwise protected-role @@ -390,6 +621,16 @@ request behavioral and architecture. ### Stage result and evidence +`portable-core-result-facts` owns the shapes and comparisons for actual binding, +performer, environment, observed capability, model/tool facts, and whether execution +matches the selected request/binding or preserves an incident mismatch. +`portable-core-stage-request` owns one expected-execution projection from the +request and resolved binding; result facts imports that projection instead of +copying request selection rules. Result facts never chooses status or outcome. +`portable-core-result-truth` alone owns status presence, evidence closure, stale +observations, outcome precedence, outputs/delta, and time. This keeps execution +comparison from silently overriding the outcome state machine. + `outcome` is `{family:"change",value:"changed"|"no-change"|"inconclusive"}` or `{family:"check",value:"passed"|"failed"|"inconclusive"}`. `reason` is `{reason_id:ID,summary?:ShortText}`. `output` is @@ -494,16 +735,30 @@ number is at least 1. Time order is `requested <= started <= finished <= recorded` with execution and `requested <= recorded` without it. Orchestrator owns sequence/history. -For completed producer results, any non-passing evidence yields -`change/inconclusive` with no output/delta. Otherwise one output yields -`change/changed`; for `git-patch`, `output.ref` is a `git_patch_ref` and `delta_ref` -is present and equals it. Delta is absent for the other artifact kinds. Empty -output/delta yields -`change/no-change`. -For completed verifier/reviewer results, output/delta are empty: any failed evidence -yields `check/failed`; otherwise any inconclusive evidence yields -`check/inconclusive`; otherwise the result is `check/passed`. Completed-inconclusive -always has a reason. +Before completed outcome reduction, evidence kinds equal the request exactly, one +item per requested kind. Producer permits only deterministic evidence. Verifier +permits deterministic plus only the requested behavioral/architecture kinds. +Reviewer permits only independent-review. `fact_gap` means a requested +provider/model/effort/prompt/skill fact is unavailable, or the selected binding +requests one or more tools and tools are unavailable. Snapshot is excluded because +it has no requested counterpart. + +| Role | Failed evidence | Inconclusive evidence or `fact_gap` | Outputs | Required outcome | +|---|---:|---:|---:|---| +| producer | one or more | yes or no | must be 0 | `change/inconclusive`; reason required, delta absent | +| producer | none | yes | must be 0 | `change/inconclusive`; reason required, delta absent | +| producer | none | no | 0 | `change/no-change`; reason and delta absent | +| producer | none | no | 1 | `change/changed`; git-patch requires equal patch output/delta, other artifact kinds forbid delta | +| producer | none | no | more than 1 | invalid | +| verifier/reviewer | one or more | yes or no | 0 | `check/failed`; failed has precedence, reason absent | +| verifier/reviewer | none | yes | 0 | `check/inconclusive`; reason required | +| verifier/reviewer | none | no | 0 | `check/passed`; reason absent | +| verifier/reviewer | none or one or more | yes or no | nonzero | invalid | + +The required reduction order is explicit: checks use failed evidence first, then +inconclusive evidence or `fact_gap`, then passed. Changes use any non-passing +evidence or `fact_gap` before changed/no-change by output count. No compound +condition may produce an outcome different from the table. For completed non-inconclusive execution, actual binding equals the corresponding projection of the selected resolved binding. Performer is an adapter actor with @@ -512,7 +767,8 @@ environment equals the request, and capability is the requested registered ID. Recorded/computed provider, model, effort, prompt, and skills equal the selected model binding; deterministic fields follow the not-applicable rule. Recorded/computed tools are a subset of requested/offered tools; -an unavailable requested fact makes the result inconclusive. Snapshot has no +an unavailable requested fact is a `fact_gap`: it blocks passed/changed/no-change +but never overrides failed check evidence in the table above. Snapshot has no requested counterpart and may be unavailable. Failed, cancelled, or completed-inconclusive execution may differ and preserves observed facts. Any other difference forbids completed non-inconclusive. Metadata kind matches actual binding. @@ -525,9 +781,9 @@ plus registered `core.review.change.v1`. Wrong performer/capability may carry on non-passing request-allowed evidence. Prior evidence enters only through `prior_evidence_refs` and cannot satisfy current evidence. -### Pure validator and delivery +### Public validator, routing, and error boundary -`scripts/core-contract.sh` exposes exactly: +The assembly child exposes exactly: ```text validate-document DOCUMENT @@ -536,58 +792,166 @@ validate-stage-run REQUEST RESOLVED_PROFILE RESULT ``` No command accepts a repository path, physical root, executable, environment map, -URL, or credential. Success is exit 0 with empty stdout. Failure is nonzero and -starts stderr with `E_USAGE`, `E_RUNTIME`, `E_PARSE`, `E_CANONICAL`, `E_LIMIT`, -`E_SHAPE`, `E_REF`, or `E_RELATION`; input bytes and local paths are not echoed. - -Tests build one valid five-document bundle and run at least 60 table-driven -mutations covering canonical roots/limits, exact shapes/enums, root-tree and unsafe -paths, canonical-JSON tree rejection, manifest/profile/source relations, the three -capabilities and five permission bounds, full package/tool/config equality, -instruction purpose/subject/input closure, dormant/protected roles, empty -deterministic skills, verifier target-revision equality, -request/result/status/time/output and patch-media rules, actual-fact incidents, -model/tool availability, evidence replay/passing-role rules, source/base and other -stale selectors, generic escapes, and all three commands. They do not read Git, -launch a process, or use a network. - -Planning estimate from the first implementation preflight: - -| Area | Normal-format working range | -|---|---:| -| `contracts.jq` | 380–755 | -| shell wrapper | 50–110 | -| readable fixtures and adversarial tests | 240–330 | -| docs, restore manifest, and CI wiring | 30–60 | -| **Likely total** | **about 800–1,100** | - -These numbers guide plan review; they do not waive or reject work. The plan explains -large differences from the range and chooses review units by responsibility, not by -an arbitrary line target. Code golf, generated long lines, copied registries, a -second parser, system-jq drift, reused production logic as the test oracle, or reduced -negative coverage are never valid ways to appear smaller. This one-concern contract -keeps one implementation PR despite the estimate; commits and review passes separate -responsibilities without creating partial product releases. A newly discovered -concern or scope change returns to the artifact gate, and no plan may invent extra -implementation PRs for this intent. +URL, credential, module path, schema path, or test switch. Success is exit 0 with +empty stdout. Failure is nonzero and starts stderr with `E_USAGE`, `E_RUNTIME`, +`E_PARSE`, `E_CANONICAL`, `E_LIMIT`, `E_SHAPE`, `E_REF`, or `E_RELATION`; input +bytes, caller paths, private temp paths, and raw tool diagnostics are not echoed. + +The public order is fixed: + +```text +usage → runtime → raw-byte limit → parse → canonical + → parsed structural limits → shape → ref → relation +``` + +Command/arity checks run before jq, SHA, package-member, or input access. An input +with more than 1,048,576 bytes returns `E_LIMIT` before parsing, even if its bytes are +also invalid JSON. An input within that byte bound reaches parsing; parsed +depth/member/string/integer limits return `E_LIMIT` only after canonical-byte proof. +The dispatcher runs one semantic layer across every supplied document before it +enters the next layer. Only after every document passes its complete self-check does +a command run its cross-document graph. + +`portable-core-ingress` owns one private error boundary for every raw-byte and temp +operation. Forced-failure proof covers at least: + +- unreadable input and the byte-limit read; +- canonicalizer and byte-comparison failure; +- SHA command failure or empty digest; +- `mktemp` failure; +- every later private-temp create, truncate, append, and redirect failure; +- driver input and validator-output write failure; +- non-1.6 jq; +- validator nonzero exit, extra output, or unknown token. + +Each failure emits only its allowed token. Stderr contains no caller path, temp path, +stub message, or fixture bytes. + +### Command-to-rule matrix + +`D(K)` is `validate-document` for kind `K`; `P` is `validate-profile-set`; `S` is +`validate-stage-run`. + +| Owner | Rule group | D | P | S | +|---|---|---|---|---| +| assembly | command form, fixed member/import guard, command routing, and public error order | applicable | applicable | applicable | +| ingress | raw-byte limit/snapshot, canonical bytes, hash, and sanitized I/O | each input | each input | each input | +| schema | parsed structural limits, envelope, primitives, shared refs, and fixed registry/policy constants | supplied document | every supplied document | every supplied document | +| profile graph | manifest self | `D(adapter_manifest)` | every manifest | — | +| profile graph | profile self and protected-role closure | `D(profile)` | profile | — | +| profile graph | resolved-profile self and source projections | `D(resolved_profile)` | resolved profile | resolved profile | +| profile graph | profile/resolved/manifest external graph | — | yes | — | +| stage request | request body, capability/permission/input/instruction/evidence self closure | `D(stage_request)` | — | request | +| stage request | request-to-resolved binding/ref relation | — | — | yes | +| result facts | actual-binding/execution/fact shapes | `D(stage_result)` when present | — | result when present | +| result facts | actual-versus-request/resolved execution assessment | — | — | yes | +| result truth | result body, status presence, stale-selector/output/evidence local shape, and started/finished/recorded local order | `D(stage_result)` | — | result | +| result truth | request/resolved refs, stale expected/difference/repository checks, requested-at time floor, exact evidence set, outcome, and execution-truth relation | — | — | yes | + +No row mixes owners. Each child spec expands its groups into rows for every stable +rule ID. Every required cell has one accepted fixture, one rule-targeted rejection, +and deterministic proof that the command calls that owner. The proof mechanism is +private test infrastructure and is never selectable through the public wrapper. +Route proof shows the owner is called; ordinary valid/invalid cases show the owner's +rule. Neither substitutes for the other. + +### Module and full-package proof + +Fixtures contain data only. They never import product registries or predicates and +never ask product code to build a valid value or choose the expected result. Expected +error, route, evidence, and outcome values are literal test data derived from this +spec. Tests use pinned jq 1.6 only to emit canonical bytes and an external SHA-256 +tool only to compute digests. A mutation that changes a referenced document rebuilds +downstream refs independently so it reaches the intended rule. + +Each private child supplies: + +- direct valid and invalid cases for every owned rule ID; +- exact-boundary and one-over cases for every owned bound; +- a closing rule/test for every assigned `review-rN-fNN` finding row; +- a closing test ID or reason for every assigned `legacy-test-NNN` assertion row; +- a data-only fixture extension; +- the current private-activation guard and restore manifest entry for every new + package/test file; +- exact-head jq 1.6 CI and an `owned rules: N/N` report with zero failures; +- independent review that does not require re-certifying a different owner. + +The schema child establishes the pinned jq 1.6 private-package CI step. Each later +child explicitly adds its own test command to that step; no wildcard discovery or +ambient executable is allowed. Constitution-path updates remain operator-owned or +arrive as `proposals/` for operator application. The first six keep the private +activation guard green; assembly replaces it only with the accepted public-package +guard described in R23. + +The assembly child additionally proves: + +- successful `validate-document` for all five kinds; +- profile-set success plus 1/8/9 manifest boundaries, missing manifests, and extras; +- stage-run success for producer, verifier, and reviewer; +- all six terminal statuses and every row of the completed outcome table, including + failed-plus-inconclusive and failed-plus-`fact_gap`; +- every command-to-rule cell and forced route; +- raw-byte and sanitized-error behavior through the public shell wrapper; +- combined-defect rows proving oversized invalid JSON returns raw `E_LIMIT`, while + within-limit invalid JSON returns `E_PARSE`, plus later-layer order pairs; +- the exact import allowlist/grammar, no `search` metadata, fake cwd, fake + `HOME/.jq`, and ambient same-name modules cannot change loaded code; every expected + directory fails if missing, not a directory, or a symlink; wrapper, ingress, root + program, and module files fail if missing, nonregular, or symlinks; +- all 31 review-finding rows and all 279 legacy-assertion rows are accounted for; and +- pinned jq 1.6 CI on the exact reviewed head. + +The final report uses semantic counters, not one undifferentiated assertion total: + +```text +owned rules: N/N +command-to-rule cells: N/N +forced routes: N/N +review findings accounted for: 31/31 +legacy assertions accounted for: 279/279 +full-package cases: N/N +failures: 0 +``` + +The 31 review rows and 279 assertion rows are migration evidence, not an oracle or +semantic acceptance target. Repeated findings remain visible even when several map +to the same new rule. Code golf, generated long lines, copied registries, a second +parser, system-jq drift, fewer negative cases, or product logic used as the test +oracle never justify a smaller child. ### Downstream handoffs and intent questions -1. **Smallest records/refs:** five documents plus the shared refs above. Evidence is - nested; policy/test/telemetry bodies stay outside core. -2. **V1 capability/permission set:** three capabilities and five permissions. - Dormant roles preserve identity/separation but cannot execute. -3. **Validator boundary:** canonical shape, lexical refs, offer/request/provenance, - role separation, and request/result/status/evidence relations. No Git truth, - authentication, execution, policy truth, or external effect. -4. **Resolver seam:** consume canonical profile/manifests plus caller-owned exact - sources, selection, repository context, and physical repo map; emit canonical - resolved profile/source claims. Resolver alone checks Git truth. -5. **Adapter-test seam:** accepted inventory and observations are runner-owned exact - artifacts supplied as verifier input/proof. Adapter-facing traffic uses only the - five core docs; runner independently executes, revalidates every core document, - recomputes assertions/Git facts, and ignores adapter self-reported verdicts. Its - test-only format is not core schema or authority. +1. **Dependency order and one clear home:** the seven children and owner table above + are exact. Raw-byte ingress is separate from request `named_input` closure. Result + facts compare observed execution; result truth alone chooses status/outcome. +2. **Package identity and loading:** `core.contracts.v1` is one semantic package. + Assembly loads only its literal root and private module directory with jq 1.6 + `-L`; external Git refs carry exact source claims when needed. +3. **Child slugs and final wait:** every independently merged part uses the exact + child slug above. Assembly waits for all six upstream accepted specs and G3 + commits before its own code and is the only child that exposes public commands. +4. **PR #183 reuse:** its final head is a read-only source snapshot. The migration + ledger assigns each moved rule/test one owner and new proof. Old round fixes remain + evidence; hard-to-read predicates, temp I/O, and result precedence are rewritten, + not copied. +5. **Proof matrices:** owned-rule cases, command-to-rule routes, completed outcome + rows, forced failures, and full-package cases are all complete independently. + Counting assertions or passing an earlier failure class cannot stand in for a + missing cell. +6. **Review ranges:** the child table replaces the old 800–1,100 one-PR estimate. + Result facts and result truth are separate because PR #183 repeatedly mixed + execution equality with outcome precedence. A range is a planning signal; a + second responsibility returns to that child's artifact gate. + +The resolver seam remains unchanged: it consumes canonical profile/manifests plus +caller-owned exact sources, selection, repository context, and physical repo map; +it emits canonical resolved-profile/source claims and alone checks Git truth. + +The adapter-test seam also remains unchanged. Accepted inventory and observations +are runner-owned exact artifacts supplied as verifier input/proof. Adapter-facing +traffic uses only the five core docs; the runner independently executes, revalidates +every core document, recomputes assertions/Git facts, and ignores adapter +self-reported verdicts. Its test-only format is not core schema or authority. The accepted adapter-test intent still promises a producer/forge 2×2 matrix. This minimal core has no executable forge capability, so that G2 must wait for a separate @@ -602,16 +966,28 @@ before any publisher can run. ## Out of scope +- Changing any v1 document, field, enum, capability, permission, canonical-byte, + digest, error-class, or claims-not-authority meaning. +- Adding a public command, dynamic module path, partial-package activation, or live + caller before `portable-core-assembly` G3. +- Creating or approving the seven child issues/plans/code in this G2 PR. The parent + plan and each child gate own those later decisions. +- Resuming, merging, rebasing, resetting, force-pushing, or otherwise changing PR + #183. Its later close-as-superseded decision follows the accepted parent plan. - Core test-inventory/result kinds, fake runner, fixture/process execution, 2×2, timeout/cleanup, and external-target smoke. - Forge, CI, execution, identity, or publisher operations; GitHub/GitLab/native transports; CR/comment/label/status; topic CAS; any external write. -- Git reads, physical repo mapping, object/mode/symlink/replacement checks, - config/tool provenance truth, authentication, credentials, grants, policy/gate - evaluation, qualification, sandbox/network enforcement, and kill switch. +- Git reads, physical repo mapping, caller-supplied Git object/mode/symlink/ + replacement truth checks, config/tool provenance truth, authentication, + credentials, grants, policy/gate evaluation, qualification, sandbox/network + enforcement, and kill switch. - Retry/reconciliation/backpressure, deployment/rollback, production incidents, telemetry/trace/usage/cost, packaging/install/migration, skill bridges, non-Git stores, or live profile activation. +- Amending `portable-profile-resolution` or another downstream artifact here. Any + downstream spec that pins the old core spec/G3 returns through its own artifact + and plan gates before implementation. - Continuing #154, closing parent #153, or claiming these records prove portability, authorization, isolation, or correct execution. @@ -626,13 +1002,27 @@ before any publisher can run. and human-gate boundaries before real use. 4. **Forge 2×2 is unresolved by design.** The existing adapter-test intent must wait or be separately rescoped; producer-only proof cannot be called its accepted 2×2. -5. **Size never trades away proof.** The estimate may move, but strict parsing, - complete relations, readable code, adversarial tests, CI, and review do not. A - materially different concern or scope returns to the artifact gate. The accepted - review-size exception changes no safety or proof requirement. +5. **Smaller children do not weaken proof.** The seven ranges are evidence-based + planning signals. Strict parsing, complete relations, readable code, adversarial + tests, exact CI, and independent review remain hard gates. A child outside its + range explains the evidence; a second responsibility returns to its artifact gate. 6. **CI is a constitution path.** Operator-driven work may edit it; unattended work must use `proposals/` and wait for application. -7. **This is user-directed high-risk design.** G2 accepts only this spec, not a plan, - implementation, proactive work, or activation. +7. **This is user-directed high-risk design.** The package defines security controls, + broad architecture, workflow dependencies, and operator-owned CI. G2 accepts this + `risk: high` classification and this spec only; it does not approve the parent + plan, any child intake, code, or activation. 8. **Nothing changes live.** `/yshifu`, manager persona, adapters, and sessions stay unchanged. Human merge remains the only merge path. +9. **Children do not inherit intake approval.** Each exact child issue needs its own + current-title/body acceptance record and full artifact chain. Parent G2/plan, + dependency links, or quoted scope prove provenance but never create `ready`. +10. **Old evidence is stale by design.** The #180 bridge ended with the G1 intent + change. PR #183 reviews and CI remain useful failure evidence, not acceptance. + `portable-profile-resolution/spec.md` still pins the old core spec/G3 and must be + amended before its own implementation can proceed. +11. **The host toolchain is operator-controlled.** The public wrapper does not claim + to defend against a malicious `jq`, SHA tool, `mktemp`, or other executable on + `PATH`; caller documents and command arguments cannot set that environment. CI + pins jq 1.6 by release-asset digest. Later control-foundation/qualification work + owns executable provenance for other environments. From 70185493641eb46436c0a977516446d4686db47b Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 29 Aug 2026 16:37:57 -0400 Subject: [PATCH 11/15] spec: bind portable core dependency closure --- work/portable-core-contracts/spec.md | 50 +++++++++++++++------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 580210b..3b07cbe 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -145,20 +145,22 @@ authority, or perform an external write. review rounds are defect evidence, not acceptance evidence. The #180 bridge ended when G1 changed the intent blob and how the work is split and delivered; it authorizes no child, branch, PR, or resume. -- **R22 — cross-child pins fail closed.** Each child spec records its direct upstream - accepted spec blobs. Each downstream high-risk plan waits for and records all - required upstream G3 tuples. A tuple is exactly - `{merge_commit, exports:[{path,mode,type,oid}]}` using that child's closed product - export list in Design. Before downstream G2 review, compare only the direct spec - blobs with current main. Before downstream plan review, code, CI review, and final - review, compare those blobs again and also require each G3 merge commit to be an - ancestor of current main and every export tuple to equal the current-main Git - entry. Any move or mismatch marks the downstream work stale and returns it through - its own G2 and high-risk plan gates; code already written is preserved, but its - review evidence is stale. Assembly performs the G3 check for all six upstream - children. Shared CI, restore manifest, activation-guard, test-harness, fixture, - and documentation paths are not product exports; every downstream exact-head/base - review instead reruns and binds their current versions. +- **R22 — cross-child pins fail closed.** Each child spec records the full required + upstream accepted-spec closure. Each downstream high-risk plan waits for and + records the full required upstream G3 closure. A G3 tuple is exactly + `{spec_blob,merge_commit,exports:[{path,mode,type,oid}]}` using that child's closed + product export list in Design. The upstream implementation PR/review/G3 record + must bind that same accepted `spec_blob`. Before downstream G2 review, compare + every recorded upstream spec blob with current main. Before downstream plan review, + code, CI review, and final review, compare those blobs again; also require every G3 + tuple's `spec_blob` to match, each merge commit to be an ancestor of current main, + and every export tuple to equal the current-main Git entry. Any move or mismatch + marks the downstream work stale and returns it through its own G2 and high-risk + plan gates; code already written is preserved, but its review evidence is stale. + Assembly performs the G3 check for all six upstream children. Shared CI, restore + manifest, activation-guard, test-harness, fixture, and documentation paths are not + product exports; every downstream exact-head/base review instead reruns and binds + their current versions. - **R23 — private state is enforced until assembly.** The schema child establishes a deterministic activation guard. Every first-six child runs it in CI and proves there is no public command/root dispatcher, install/export/profile wiring, user @@ -193,14 +195,15 @@ The diagram shows scheduling order, not every source import. The table below is the exact direct-dependency list and its full upstream G3 closure. Children on the same level may proceed in parallel after their common prerequisites merge. -Each child spec frontmatter adds `upstream-spec-blobs`, an exact direct-dependency -map of `slug → git-blob` (`{}` when none). Each child plan adds `upstream-g3`, a -full-closure map of `slug → {merge_commit,exports:[{path,mode,type,oid}]}`. G2 review -compares the first map with the current-main child spec files. Plan review, code, CI -review, and final review also compare the second map under R22. No issue comment or -branch state substitutes for those exact identities. +Each child spec frontmatter adds `upstream-spec-blobs`, an exact full-closure map of +`slug → git-blob` (`{}` when none), using the `Full upstream closure` column for +the slug set. Each child plan adds `upstream-g3`, a map of +`slug → {spec_blob,merge_commit,exports:[{path,mode,type,oid}]}` over that same full +closure. G2 review compares the first map with the current-main child spec files. +Plan review, code, CI review, and final review also compare the second map under R22. +No issue comment or branch state substitutes for those exact identities. -| Child | One private responsibility | Direct dependencies | All required upstream G3 | Expected net new lines: product + owned proof | +| Child | One private responsibility | Direct dependencies | Full upstream closure: specs + G3 | Expected net new lines: product + owned proof | |---|---|---|---|---:| | `portable-core-schema` | parsed depth/member/string/integer limits; primitives; shared refs; envelopes; document-kind registry; one declarative role/capability/permission/evidence policy table | none | none | 360–460 | | `portable-core-ingress` | raw-byte limit and bounded snapshot, jq 1.6 canonical bytes, hashes, private temp I/O, sanitized errors | schema | schema | 230–320 | @@ -739,9 +742,8 @@ Before completed outcome reduction, evidence kinds equal the request exactly, on item per requested kind. Producer permits only deterministic evidence. Verifier permits deterministic plus only the requested behavioral/architecture kinds. Reviewer permits only independent-review. `fact_gap` means a requested -provider/model/effort/prompt/skill fact is unavailable, or the selected binding -requests one or more tools and tools are unavailable. Snapshot is excluded because -it has no requested counterpart. +provider/model/effort/prompt/skill fact is unavailable, or the tools fact is +unavailable. Snapshot is excluded because it has no requested counterpart. | Role | Failed evidence | Inconclusive evidence or `fact_gap` | Outputs | Required outcome | |---|---:|---:|---:|---| From 8c3c6e21c161d183ce9f762da4a4c11c320510b6 Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 29 Aug 2026 16:54:26 -0400 Subject: [PATCH 12/15] spec: make portable core generations atomic --- work/portable-core-contracts/spec.md | 179 ++++++++++++++++----------- 1 file changed, 109 insertions(+), 70 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 3b07cbe..7d3704b 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -90,23 +90,26 @@ authority, or perform an external write. The operator remains the only merge authority. - **R16 — partial packages stay private.** The first six children add only private package members, test drivers, fixtures, and their exact CI proof. They do not add - or document a public validator command, install path, profile selection, or live - caller. Only `portable-core-assembly` may add the public shell command, root + or change a public validator command, install path, profile selection, or live + caller for the generation under construction. During an upgrade the prior public + package remains unchanged and selected. Only `portable-core-assembly` may add or + switch the public shell command, root dispatcher, full-package user documentation, final restore instructions, and three public command forms. Every child immediately lists its own restore-critical files in `ci/required-files.txt`; assembly owns only the final package/user view. - **R17 — code loading is fixed.** The assembly wrapper resolves its own physical - repository root and loads one literal root program plus one literal private module - directory. It passes jq 1.6 exactly one `-L` directory, which disables jq's builtin - module search list. Product jq files may use only exact statements of the form + repository root and contains one literal accepted generation ID. It loads that + generation's one literal root program, one literal ingress library, and one literal + private module directory. It passes jq 1.6 exactly one `-L` directory, which + disables jq's builtin module search list. Product jq files may use only exact statements of the form `import "NAME" as NAME;`, where `NAME` is one of the five accepted module names. `include`, import metadata (including `search`), JSON imports, and names containing `/`, `.`, `..`, `~`, `$ORIGIN`, or backslash are forbidden. Caller arguments, cwd, `HOME`, module-search environment, config, document content, URLs, and ambient jq modules cannot select a jq module or shell library. The resolved repository root and every package-path directory component are real directories reached without a - symlink. `scripts/core-contract.sh`, `scripts/lib/core-ingress.sh`, - `core/v1/contracts.jq`, and every fixed module are regular non-symlink files inside + symlink. The stable `scripts/core-contract.sh` wrapper plus the selected generation's + ingress library, root program, and every module are regular non-symlink files inside that same physical repository. Any mismatch stops as `E_RUNTIME`. These restrictions close both search mechanisms documented by the [jq 1.6 module rules](https://jqlang.org/manual/v1.6/#modules): the default list @@ -147,27 +150,49 @@ authority, or perform an external write. authorizes no child, branch, PR, or resume. - **R22 — cross-child pins fail closed.** Each child spec records the full required upstream accepted-spec closure. Each downstream high-risk plan waits for and - records the full required upstream G3 closure. A G3 tuple is exactly - `{spec_blob,merge_commit,exports:[{path,mode,type,oid}]}` using that child's closed - product export list in Design. The upstream implementation PR/review/G3 record - must bind that same accepted `spec_blob`. Before downstream G2 review, compare - every recorded upstream spec blob with current main. Before downstream plan review, - code, CI review, and final review, compare those blobs again; also require every G3 - tuple's `spec_blob` to match, each merge commit to be an ancestor of current main, - and every export tuple to equal the current-main Git entry. Any move or mismatch - marks the downstream work stale and returns it through its own G2 and high-risk - plan gates; code already written is preserved, but its review evidence is stale. - Assembly performs the G3 check for all six upstream children. Shared CI, restore - manifest, activation-guard, test-harness, fixture, and documentation paths are not - product exports; every downstream exact-head/base review instead reruns and binds - their current versions. + records the full required upstream G3 closure. Define + `g3_ref={spec_blob,generation_id,merge_commit, + generation_exports:[{path,mode,type,oid}], + activation_exports:[{path,mode,type,oid}]}` and + `g3_record={self:g3_ref,built_against:{slug:g3_ref}}`. Each upstream + implementation PR/review/G3 record binds its accepted spec, generation, own closed + product exports, and the exact full dependency closure it was built against. + Before downstream G2 review, compare every recorded upstream spec blob and + generation ID with current main. Before downstream plan review, code, CI review, + and final review, compare those values again and require every G3 self ref to match + current main. For each G3 record, `built_against` must equal the downstream map's + self refs for that child's full prerequisite closure. Any move, generation mix, or + mismatch marks the downstream work stale and returns it through its own G2 and + high-risk plan gates; code already written is preserved, but its review evidence + is stale. Assembly performs the G3 check for all six upstream children. Shared CI, + restore manifest, activation-guard, test-harness, fixture, and documentation paths + are not product exports; every downstream exact-head/base review instead reruns + and binds their current versions. - **R23 — private state is enforced until assembly.** The schema child establishes a - deterministic activation guard. Every first-six child runs it in CI and proves - there is no public command/root dispatcher, install/export/profile wiring, user - documentation claiming a complete package, or non-test caller of private modules - or ingress. Only fixed private test drivers are allowed. Assembly G3 deliberately - replaces that expectation with a guard that permits the one public wrapper/root - while still proving no live profile, manager, template, or install path calls it. + deterministic generation-aware activation guard. Before the first assembly, it + requires no public wrapper/root and no non-test caller. During an upgrade, every + first-six child permits the one stable wrapper and already published immutable + generation roots, but requires that wrapper to keep selecting exactly one previous + complete generation. It forbids any public/non-test caller or user documentation + from naming the generation under construction, and that generation has no root + program until assembly. Only fixed private test drivers may load its members. + Assembly G3 adds the new root and either adds or changes the one wrapper. The + post-assembly guard permits multiple immutable generation roots but exactly one + wrapper selecting one complete generation, while still proving no live profile, + manager, template, or + install path calls it. +- **R24 — generations switch atomically.** The parent plan chooses and records one + unused `generation_id` matching `g-[0-9a-f]{64}`. It is public, not a secret or + authority value, and can never be reused. All seven child artifacts and G3 records + name that exact ID. Their implementation product files write only below its inactive + `core/v1/generations//` subtree, except for shared proof files and + the stable public wrapper. Generation-scoped exports become permanently immutable + when assembly publishes them. The wrapper is a separate activation export: only + assembly may change it, and only in the final switch commit after complete proof. + Any upgrade parent plan pins the current wrapper blob and selected generation, + chooses a newly accepted unused generation ID, builds a complete inactive + generation, and reruns all affected child and assembly gates. The old public + generation remains selected until that atomic wrapper switch. ## Design @@ -195,12 +220,13 @@ The diagram shows scheduling order, not every source import. The table below is the exact direct-dependency list and its full upstream G3 closure. Children on the same level may proceed in parallel after their common prerequisites merge. -Each child spec frontmatter adds `upstream-spec-blobs`, an exact full-closure map of -`slug → git-blob` (`{}` when none), using the `Full upstream closure` column for -the slug set. Each child plan adds `upstream-g3`, a map of -`slug → {spec_blob,merge_commit,exports:[{path,mode,type,oid}]}` over that same full -closure. G2 review compares the first map with the current-main child spec files. -Plan review, code, CI review, and final review also compare the second map under R22. +Each child spec frontmatter adds the parent-plan-recorded `generation-id` plus +`upstream-spec-blobs`, an exact full-closure map of `slug → git-blob` (`{}` when +none), using the `Full upstream closure` column for the slug set. Each child plan +adds `upstream-g3`, a map of `slug → g3_record` over that same full closure. G2 +review compares generation and the spec map with current main. Plan review, code, +CI review, and final review also compare every G3 record under R22, including that +each record's `built_against` refs exactly match the selected upstream generation. No issue comment or branch state substitutes for those exact identities. | Child | One private responsibility | Direct dependencies | Full upstream closure: specs + G3 | Expected net new lines: product + owned proof | @@ -230,12 +256,12 @@ document shapes and cross-document relations. This spec fixes the private package layout: ```text -core/v1/modules/schema.jq -core/v1/modules/profile_graph.jq -core/v1/modules/stage_request.jq -core/v1/modules/result_facts.jq -core/v1/modules/result_truth.jq -scripts/lib/core-ingress.sh +core/v1/generations//modules/schema.jq +core/v1/generations//modules/profile_graph.jq +core/v1/generations//modules/stage_request.jq +core/v1/generations//modules/result_facts.jq +core/v1/generations//modules/result_truth.jq +core/v1/generations//core-ingress.sh ``` Allowed jq imports are exact: @@ -252,36 +278,39 @@ Allowed jq imports are exact: CI statically rejects every other import/include/module directive or metadata form and behaviorally proves fake search roots cannot change the loaded program. -Product export ownership is also closed and disjoint: - -| Child | Immutable product exports used by downstream pins | -|---|---| -| `portable-core-schema` | `core/v1/modules/schema.jq` | -| `portable-core-ingress` | `scripts/lib/core-ingress.sh` | -| `portable-core-profile-graph` | `core/v1/modules/profile_graph.jq` | -| `portable-core-stage-request` | `core/v1/modules/stage_request.jq` | -| `portable-core-result-facts` | `core/v1/modules/result_facts.jq` | -| `portable-core-result-truth` | `core/v1/modules/result_truth.jq` | -| `portable-core-assembly` | `core/v1/contracts.jq`, `scripts/core-contract.sh` | - -No other path may appear in an `upstream-g3` export tuple. A later child never edits -an earlier child's product export. A needed change returns through that earlier -child's artifact and plan gates, then refreshes every affected downstream pin. -Shared proof and aggregate paths may change only within accepted child scope and are -revalidated on the current head/base; they never masquerade as another child's -immutable product export. +Within one planned generation, export ownership is closed and disjoint: + +| Child | `generation_exports` — immutable after publication | `activation_exports` — assembly switch only | +|---|---|---| +| `portable-core-schema` | `core/v1/generations//modules/schema.jq` | none | +| `portable-core-ingress` | `core/v1/generations//core-ingress.sh` | none | +| `portable-core-profile-graph` | `core/v1/generations//modules/profile_graph.jq` | none | +| `portable-core-stage-request` | `core/v1/generations//modules/stage_request.jq` | none | +| `portable-core-result-facts` | `core/v1/generations//modules/result_facts.jq` | none | +| `portable-core-result-truth` | `core/v1/generations//modules/result_truth.jq` | none | +| `portable-core-assembly` | `core/v1/generations//contracts.jq` | `scripts/core-contract.sh` | + +No other path may appear in either G3 export list. Before assembly publishes the +generation, a needed generation-export change returns through the owning child's +artifact and plan gates, then refreshes every affected downstream pin/record. After +assembly publishes it, no child edits any generation export; an upgrade follows R24 +under a new generation. The stable wrapper may change only as assembly's final +activation export. Shared proof and aggregate paths may change only within accepted +child scope and are revalidated on the current head/base; they never masquerade as a +generation or activation export. The assembly child alone adds: ```text -core/v1/contracts.jq +core/v1/generations//contracts.jq scripts/core-contract.sh ``` `contracts.jq` uses literal, namespaced imports. `core-contract.sh` resolves its -own physical repository root, sources only the fixed ingress library, and runs jq -with the literal module root `core/v1/modules`. The public command accepts no code, -module, schema, search-path, or test-hook argument. +own physical repository root, contains the literal accepted generation ID, sources +only that generation's fixed ingress library, and runs jq with that generation's +literal module root. The public command accepts no code, generation, module, schema, +search-path, or test-hook argument. ### Parent plan, child intake, and the failed attempt @@ -292,12 +321,14 @@ ranges, PR #183 migration ledger ownership, and PR #183 disposition. That parent plan creates no `ready` state and authorizes no new `ystack/impl/portable-core-contracts` work. -After the parent plan merges, each child issue receives its own exact-title/body -user-directed acceptance record and complete intent → spec-with-risk → high-risk -plan → implementation chain. Child artifact and plan PRs track their child issue -and may also track #155. Each child implementation closes its own issue. With -respect to parent #155, the first six implementation PRs use `Tracks #155`; the -assembly implementation uses `Closes #155`. +The parent plan includes the exact R24 generation ID in all seven child issue drafts +and verifies that its subtree does not already exist. After the plan merges, each +child issue receives its own exact-title/body user-directed acceptance record and +complete intent → spec-with-risk → high-risk plan → implementation chain. Child +artifact and plan PRs track their child issue and may also track #155. Each child +implementation closes its own issue. With respect to parent #155, the first six +implementation PRs use `Tracks #155`; the assembly implementation uses +`Closes #155`. PR #183 remains frozen in its exact paused state after the final review round while G2 and the parent plan are under review: head @@ -881,9 +912,9 @@ Each private child supplies: The schema child establishes the pinned jq 1.6 private-package CI step. Each later child explicitly adds its own test command to that step; no wildcard discovery or ambient executable is allowed. Constitution-path updates remain operator-owned or -arrive as `proposals/` for operator application. The first six keep the private -activation guard green; assembly replaces it only with the accepted public-package -guard described in R23. +arrive as `proposals/` for operator application. The first six keep the +generation-aware pre-assembly guard green. Assembly alone advances the guard's +selected generation under R23/R24 after full-package proof. The assembly child additionally proves: @@ -900,6 +931,10 @@ The assembly child additionally proves: `HOME/.jq`, and ambient same-name modules cannot change loaded code; every expected directory fails if missing, not a directory, or a symlink; wrapper, ingress, root program, and module files fail if missing, nonregular, or symlinks; +- the wrapper's literal generation cannot be changed by arguments or environment; a + second incomplete/inactive generation never affects the selected one; an upgrade + diff leaves the old generation untouched and changes the wrapper only after the + new generation's complete assembly proof; - all 31 review-finding rows and all 279 legacy-assertion rows are accounted for; and - pinned jq 1.6 CI on the exact reviewed head. @@ -1028,3 +1063,7 @@ before any publisher can run. `PATH`; caller documents and command arguments cannot set that environment. CI pins jq 1.6 by release-asset digest. Later control-foundation/qualification work owns executable provenance for other environments. +12. **Published generations are immutable.** A repair never edits the active + generation in place. It creates and qualifies a complete new generation, then + assembly switches the stable wrapper once. Removing old generations is a later + migration/restore decision and is not bundled with activation. From 3a23d68ed3ffe8ede06896585d8bf430088e0457 Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 29 Aug 2026 17:10:11 -0400 Subject: [PATCH 13/15] spec: close portable core generation gates --- work/portable-core-contracts/spec.md | 98 ++++++++++++++++++---------- 1 file changed, 64 insertions(+), 34 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 7d3704b..273aba8 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -160,14 +160,17 @@ authority, or perform an external write. Before downstream G2 review, compare every recorded upstream spec blob and generation ID with current main. Before downstream plan review, code, CI review, and final review, compare those values again and require every G3 self ref to match - current main. For each G3 record, `built_against` must equal the downstream map's - self refs for that child's full prerequisite closure. Any move, generation mix, or - mismatch marks the downstream work stale and returns it through its own G2 and - high-risk plan gates; code already written is preserved, but its review evidence - is stale. Assembly performs the G3 check for all six upstream children. Shared CI, - restore manifest, activation-guard, test-harness, fixture, and documentation paths - are not product exports; every downstream exact-head/base review instead reruns - and binds their current versions. + current main: `merge_commit` is the operator-accepted merge commit and is an + ancestor of current main, and both export lists equal current-main Git entries. + The append-only registry also contains the exact generation record from R24. For + each G3 record, `built_against` must equal the downstream map's self refs for that + child's full prerequisite closure. Any move, generation mix, or mismatch marks the + downstream work stale and returns it through its own G2 and high-risk plan gates; + code already written is preserved, but its review evidence is stale. Assembly + performs the G3 check for all six upstream children. Shared CI, restore manifest, + activation-guard, test-harness, fixture, and documentation paths are not product + exports; every downstream exact-head/base review instead reruns and binds their + current versions. The generation registry is separately checked under R24. - **R23 — private state is enforced until assembly.** The schema child establishes a deterministic generation-aware activation guard. Before the first assembly, it requires no public wrapper/root and no non-test caller. During an upgrade, every @@ -176,23 +179,33 @@ authority, or perform an external write. complete generation. It forbids any public/non-test caller or user documentation from naming the generation under construction, and that generation has no root program until assembly. Only fixed private test drivers may load its members. - Assembly G3 adds the new root and either adds or changes the one wrapper. The - post-assembly guard permits multiple immutable generation roots but exactly one - wrapper selecting one complete generation, while still proving no live profile, - manager, template, or - install path calls it. + Assembly's pre-switch phase adds the new root while the wrapper remains absent or + selects the old generation. After private proof, the switch commit either adds or + changes the one wrapper. The post-switch guard permits multiple immutable + generation roots but exactly one wrapper selecting one complete generation, while + still proving no live profile, manager, template, or install path calls it. - **R24 — generations switch atomically.** The parent plan chooses and records one unused `generation_id` matching `g-[0-9a-f]{64}`. It is public, not a secret or - authority value, and can never be reused. All seven child artifacts and G3 records - name that exact ID. Their implementation product files write only below its inactive - `core/v1/generations//` subtree, except for shared proof files and - the stable public wrapper. Generation-scoped exports become permanently immutable - when assembly publishes them. The wrapper is a separate activation export: only - assembly may change it, and only in the final switch commit after complete proof. - Any upgrade parent plan pins the current wrapper blob and selected generation, - chooses a newly accepted unused generation ID, builds a complete inactive - generation, and reruns all affected child and assembly gates. The old public - generation remains selected until that atomic wrapper switch. + authority value, and can never be reused. `core/v1/generation-registry.json` is a + canonical, restore-critical, append-only record with exact entries + `{generation_id,parent_spec_blob,parent_plan_merge_commit}`. The parent plan checks + the ID is absent; the schema child creates or appends its exact entry. CI requires + unique IDs and every prior main entry to remain an unchanged ordered prefix, so + deleting an old generation subtree never permits ID reuse. All seven child + artifacts and G3 records name that exact ID. Their implementation product files + write only below its inactive `core/v1/generations//` subtree, + except for shared proof/registry files and the stable public wrapper. + Generation-scoped exports become permanently immutable when assembly publishes + them. The wrapper is a separate activation export. Assembly first lands the new + root and proves the complete generation through private test drivers while the + stable wrapper is absent or still selects the old generation. One later switch + commit then adds the wrapper for first publication or changes only its literal + generation ID for an upgrade. Mandatory CI and independent review run again on + that exact post-switch head through the public wrapper; only that evidence may + produce `merge-ready` and human merge. Any upgrade parent plan pins the current + wrapper blob and selected generation, chooses a newly accepted unused generation + ID, and repeats both proof phases. The old public generation remains selected on + main until the final assembly merge. ## Design @@ -231,7 +244,7 @@ No issue comment or branch state substitutes for those exact identities. | Child | One private responsibility | Direct dependencies | Full upstream closure: specs + G3 | Expected net new lines: product + owned proof | |---|---|---|---|---:| -| `portable-core-schema` | parsed depth/member/string/integer limits; primitives; shared refs; envelopes; document-kind registry; one declarative role/capability/permission/evidence policy table | none | none | 360–460 | +| `portable-core-schema` | parsed depth/member/string/integer limits; primitives; shared refs; envelopes; document-kind registry; one declarative role/capability/permission/evidence policy table; append-only generation-registry entry | none | none | 360–460 | | `portable-core-ingress` | raw-byte limit and bounded snapshot, jq 1.6 canonical bytes, hashes, private temp I/O, sanitized errors | schema | schema | 230–320 | | `portable-core-profile-graph` | manifest/profile/resolved-profile exact body shapes, self relations, and supplied-document graph relations | schema | schema | 360–480 | | `portable-core-stage-request` | request exact body shape, capability arguments, permissions, target/input/instruction/evidence closure, request-to-binding relation | schema, profile graph | schema, profile graph | 300–400 | @@ -262,6 +275,7 @@ core/v1/generations//modules/stage_request.jq core/v1/generations//modules/result_facts.jq core/v1/generations//modules/result_truth.jq core/v1/generations//core-ingress.sh +core/v1/generation-registry.json ``` Allowed jq imports are exact: @@ -321,10 +335,12 @@ ranges, PR #183 migration ledger ownership, and PR #183 disposition. That parent plan creates no `ready` state and authorizes no new `ystack/impl/portable-core-contracts` work. -The parent plan includes the exact R24 generation ID in all seven child issue drafts -and verifies that its subtree does not already exist. After the plan merges, each -child issue receives its own exact-title/body user-directed acceptance record and -complete intent → spec-with-risk → high-risk plan → implementation chain. Child +The parent plan includes the exact R24 generation ID in all seven child issue drafts, +verifies it is absent from the append-only registry, and verifies its subtree does +not already exist. For an upgrade it also records the current wrapper blob and +selected generation. After the plan merges, each child issue receives its own +exact-title/body user-directed acceptance record and complete intent → spec-with-risk +→ high-risk plan → implementation chain. Child artifact and plan PRs track their child issue and may also track #155. Each child implementation closes its own issue. With respect to parent #155, the first six implementation PRs use `Tracks #155`; the assembly implementation uses @@ -909,12 +925,26 @@ Each private child supplies: - exact-head jq 1.6 CI and an `owned rules: N/N` report with zero failures; - independent review that does not require re-certifying a different owner. -The schema child establishes the pinned jq 1.6 private-package CI step. Each later -child explicitly adds its own test command to that step; no wildcard discovery or -ambient executable is allowed. Constitution-path updates remain operator-owned or -arrive as `proposals/` for operator application. The first six keep the -generation-aware pre-assembly guard green. Assembly alone advances the guard's -selected generation under R23/R24 after full-package proof. +The schema child establishes the pinned jq 1.6 private-package CI step, appends the +exact R24 registry entry, and proves the prior registry is an unchanged prefix with +unique generation IDs. Each later child verifies that exact entry and explicitly +adds its own test command to the CI step; no wildcard discovery or ambient executable +is allowed. Constitution-path updates remain operator-owned or arrive as +`proposals/` for operator application. The first six keep the generation-aware +pre-assembly guard green. Assembly alone advances the guard's selected generation +under R23/R24 after full-package proof. + +Assembly uses two explicit proof boundaries. Its pre-switch head contains the new +generation root, integration tests, CI, docs, and restore changes, but the stable +wrapper is absent for first publication or still selects the old generation for an +upgrade. CI and a read-only independent review prove the new generation through +private assembly drivers and record that exact head. A later single-parent switch +commit has that head as its parent and changes only `scripts/core-contract.sh`: it +adds the wrapper for first publication or changes only the literal generation ID for +an upgrade. CI and independent review then run through the public wrapper on the +exact post-switch head/base. Only the post-switch evidence can create +`merge-ready`; any other switch-commit path or diff returns to the assembly plan +gate. The assembly child additionally proves: From 5c7172fb4ad084b229a7b4ec7c494890bdaf2683 Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 29 Aug 2026 17:26:22 -0400 Subject: [PATCH 14/15] spec: make wrapper registration atomic --- work/portable-core-contracts/spec.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 273aba8..17b2f20 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -96,7 +96,10 @@ authority, or perform an external write. switch the public shell command, root dispatcher, full-package user documentation, final restore instructions, and three public command forms. Every child immediately lists its own restore-critical - files in `ci/required-files.txt`; assembly owns only the final package/user view. + files in `ci/required-files.txt`, except that first-publication assembly adds the + new public wrapper and its exact manifest entry together in the R24 switch commit. + During an upgrade that entry already exists and stays byte-identical. Assembly + owns only the final package/user view. - **R17 — code loading is fixed.** The assembly wrapper resolves its own physical repository root and contains one literal accepted generation ID. It loads that generation's one literal root program, one literal ingress library, and one literal @@ -198,11 +201,14 @@ authority, or perform an external write. Generation-scoped exports become permanently immutable when assembly publishes them. The wrapper is a separate activation export. Assembly first lands the new root and proves the complete generation through private test drivers while the - stable wrapper is absent or still selects the old generation. One later switch - commit then adds the wrapper for first publication or changes only its literal - generation ID for an upgrade. Mandatory CI and independent review run again on - that exact post-switch head through the public wrapper; only that evidence may - produce `merge-ready` and human merge. Any upgrade parent plan pins the current + stable wrapper is absent or still selects the old generation. On first publication, + the later switch commit changes exactly two paths: it adds + `scripts/core-contract.sh` and adds the exact `scripts/core-contract.sh` entry to + `ci/required-files.txt`. On an upgrade, the entry must already exist and remain + byte-identical, and the switch commit changes only the wrapper's literal generation + ID. The structure check, public-wrapper CI, and independent review run again on + that exact post-switch head; only that evidence may produce `merge-ready` and human + merge. Any upgrade parent plan pins the current wrapper blob and selected generation, chooses a newly accepted unused generation ID, and repeats both proof phases. The old public generation remains selected on main until the final assembly merge. @@ -939,10 +945,12 @@ generation root, integration tests, CI, docs, and restore changes, but the stabl wrapper is absent for first publication or still selects the old generation for an upgrade. CI and a read-only independent review prove the new generation through private assembly drivers and record that exact head. A later single-parent switch -commit has that head as its parent and changes only `scripts/core-contract.sh`: it -adds the wrapper for first publication or changes only the literal generation ID for -an upgrade. CI and independent review then run through the public wrapper on the -exact post-switch head/base. Only the post-switch evidence can create +commit has that head as its parent. For first publication it changes exactly two +paths: it adds `scripts/core-contract.sh` and adds that exact entry to +`ci/required-files.txt`. For an upgrade it changes only the wrapper's literal +generation ID; the existing manifest entry remains byte-identical. The structure +check, public-wrapper CI, and independent review then run on the exact post-switch +head/base. Only the post-switch evidence can create `merge-ready`; any other switch-commit path or diff returns to the assembly plan gate. From 8d24727ebf6cdbc0f94f527f35fb5007a23f4a49 Mon Sep 17 00:00:00 2001 From: ci Date: Sat, 29 Aug 2026 18:38:08 -0400 Subject: [PATCH 15/15] spec: correct review ledger denominator --- work/portable-core-contracts/spec.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/work/portable-core-contracts/spec.md b/work/portable-core-contracts/spec.md index 17b2f20..c6511d9 100644 --- a/work/portable-core-contracts/spec.md +++ b/work/portable-core-contracts/spec.md @@ -135,15 +135,15 @@ authority, or perform an external write. Tests keep expected verdicts as literal data and never use product policy as their oracle. The parent plan freezes two inventories from PR #183 head `ab4a7082f02e67b5748c5c54b9214f37d222f53f`: - 31 discrete review-finding rows from comments 5463604326, 5463851247, - 5464015820, and 5464192510, plus the 279 assertion results emitted in execution - order by `scripts/test/core-contract.test.sh` at that head. Finding IDs are + 34 discrete review-finding rows from comments 5463604326 (16), 5463851247 (9), + 5464015820 (5), and 5464192510 (4), plus the 279 assertion results emitted in + execution order by `scripts/test/core-contract.test.sh` at that head. Finding IDs are `review-rN-fNN`; assertion IDs are `legacy-test-001` through `legacy-test-279` with their original output label. Repeated findings remain separate rows but may point to the same new owner/rule. Every finding maps to one owner and closing rule/test or a reasoned superseding rule. Every assertion maps to one owner; `ported` and `replaced-by` name exact new rule/test IDs, while - `invalid-old-positive` gives a reason. The required denominators are 31/31 and + `invalid-old-positive` gives a reason. The required denominators are 34/34 and 279/279; neither count proves semantic completeness by itself. - **R21 — PR #183 is evidence, not implementation.** No child branches from, merges, or wholesale cherry-picks PR #183. Code and tests may move only after receiving one @@ -973,7 +973,7 @@ The assembly child additionally proves: second incomplete/inactive generation never affects the selected one; an upgrade diff leaves the old generation untouched and changes the wrapper only after the new generation's complete assembly proof; -- all 31 review-finding rows and all 279 legacy-assertion rows are accounted for; and +- all 34 review-finding rows and all 279 legacy-assertion rows are accounted for; and - pinned jq 1.6 CI on the exact reviewed head. The final report uses semantic counters, not one undifferentiated assertion total: @@ -982,13 +982,13 @@ The final report uses semantic counters, not one undifferentiated assertion tota owned rules: N/N command-to-rule cells: N/N forced routes: N/N -review findings accounted for: 31/31 +review findings accounted for: 34/34 legacy assertions accounted for: 279/279 full-package cases: N/N failures: 0 ``` -The 31 review rows and 279 assertion rows are migration evidence, not an oracle or +The 34 review rows and 279 assertion rows are migration evidence, not an oracle or semantic acceptance target. Repeated findings remain visible even when several map to the same new rule. Code golf, generated long lines, copied registries, a second parser, system-jq drift, fewer negative cases, or product logic used as the test