From ee9d5f567d696b5da5db2ece59fddf8927c6469b Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Aug 2026 15:47:39 -0400 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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: