Skip to content

docs(change): propose mediated single-file effect contract (CR-OC-001A) - #119

Merged
coreytshaffer merged 3 commits into
mainfrom
cr-oc-001a-effect-contract
Jul 28, 2026
Merged

coreytshaffer merged 3 commits into
mainfrom
cr-oc-001a-effect-contract

Conversation

@coreytshaffer

@coreytshaffer coreytshaffer commented Jul 28, 2026 •

Copy link
Copy Markdown
Owner

Requirements proposal only. Implementation authority: none. No code, tests, schema, CLI, runtime integration, IPC, file access, OpenClaw installation, or capability behavior is changed. Three documentation files, 727 insertions.

CR-OC-001A defines how one exact single-file content transition is represented, validated, and bound — the first of five slices in the mediated OpenClaw experiment. The proposed module is pure: it reads no file, writes no file, opens no connection, and touches no database.

What the contract fixes

Concern Decision
Tool-facing identifier target_file_id, never a caller-supplied path
canonical_relpath Integrity-bound for evidence consistency, but never a resolution channel — resolution is exclusively through target_file_id
Content handling proposed_bytes are hashed transiently to verify sha256(exact_proposed_bytes) == expected_post_digest, then dropped. The rule is retention, not "never digested"
Byte fidelity UTF-8 validity is a gate, not a normalization step. No Unicode normalization, newline conversion, or BOM insertion/removal. content_size_bytes measures the original sequence
Context declared_context_digest and broker_connection_id stay structurally separate in the effect, the projection, and the API
Canonical objects Five versioned, closed-field schemas with the domain discriminator inside the digest input
Capability mapping Maps onto merged CR-YK-002 without changing AuthorizationRequest or the capability schema
Replay Pure classification function; enforcement and durable state belong to CR-OC-001B

Path traversal, symlink handling, broker availability, capability lifecycle, and execution outcomes are deliberately absent from the validation vocabulary. A reason code for a condition the module cannot observe is a false capability claim in vocabulary form.

Provenance is asserted, never established

Field This slice establishes This slice cannot establish
broker_connection_id a distinct field participating in the effect digest that it was broker-generated or bound to any connection
declared_invocation_context canonical shape and digest that any declared value is true
target_file_id syntax and descriptor shape that it came from the trusted allowlist

A pure module cannot tell a broker-minted identifier from a forged one, and digesting a value makes it tamper-evident without making it true. broker-generated and broker-authenticated are reserved for CR-OC-001D.

Canonical objects

triagecore.mediated_file_descriptor.v1
triagecore.mediated_declared_context.v1     -> declared_context_digest
triagecore.mediated_file_effect.v1          -> effect_digest / scope_digest
triagecore.mediated_client_request.v1       -> request_digest
triagecore.mediated_plan_linkage.v1         -> plan_body_digest

Closed field sets; every field required and non-null, so an omitted-key form cannot exist. Domain separation follows the house pattern from triage_core/governed_decision.py — a schema field inside the canonicalized envelope, not a byte prefix. Cross-domain distinctness is a required test, not an assumption.

Connection binding runs through the whole sequence

Both scope_digest and request_digest cover broker_connection_id. Without it in request identity, the same client_request_id and request_digest could recur unchanged after a reconnect while the authorized effect had changed, and a reservation store would report idempotent replay for authority issued against a dead connection.

connection established
  -> broker_connection_id assigned
  -> proposal/effect canonicalized
  -> request_digest calculated
  -> client request reserved
  -> authorization/capability issued
  -> capability claimed
  -> effect executed

Reservation, not merely issuance, sits downstream of connection establishment. A repeated client_request_id on a different connection is request_id_reuse_mismatch, not an idempotent replay.

Replay classification is pure

classify_request_replay(
    existing: RequestBinding | None,
    incoming: RequestBinding,
) -> new_request | idempotent_replay | request_id_reuse_mismatch

A stateless module cannot both own request_id_reuse_mismatch and store nothing, so comparison and storage are split: the comparison lives here and is testable, the storage lives in CR-OC-001B. None is the explicit absence sentinel; the "optional values forbidden" rule governs the five canonical digest objects only. A non-null existing whose ID differs from incoming cannot arise from a correct caller and raises rather than returning a classification, matching how CapabilityClaimStore.claim treats a blank claimant.

Recorded as a CR-OC-001B obligation because it is not representable here: the reservation row must bind capability_id one-to-one with client_request_id. capability_id appears nowhere in this slice.

Bounded acceptance claim

TriageCore can deterministically represent a single-file content-replacement effect and bind it to exact pre- and post-content digests, a unique client request, a client-declared invocation context, and a distinct broker-connection identifier field whose provenance and connection binding are not established by this slice, while producing a privacy-safe persistent projection.

It may not be used to support: that OpenClaw is contained; that the context is authenticated; that the broker-connection identifier was broker-generated; that the target file identifier came from a trusted allowlist; that replay is prevented in practice; that paths are safe; that a capability was claimed; that any file was changed; or that OS privilege separation exists.

The content claim is also not a filesystem claim. Timestamps and file identity may change during replacement; owner, DACL, regular-file status, and workspace containment are left to CR-OC-001C with their own tests.

Tests

18 test-contract items and nine mutants, each with a designated killer test. Following the CR-YK-002 precedent, a mutant check counts only when the test is shown to fail against the mutated version.

The declared-identity mutant is recorded as weaker than the rest: it is a naming and structure defect rather than a behavioral one, so its test asserts the projection key set and the absence of any accessor presenting declared context as authenticated identity. A determined rename could still defeat it. Reviewers, not tests, are the control there.

Downstream

CR-OC-001A — pure effect contract          (this proposal)
CR-OC-001B — atomic client-request reservation
CR-OC-001C — constrained single-file replacement executor
CR-OC-001D — privilege-separated broker and hardened named pipe
CR-OC-001E — exclusive OpenClaw tool and effective-schema evaluation

CR-OC-001D requirements, recorded here and satisfied by no earlier slice:

  • PIPE_REJECT_REMOTE_CLIENTS on creation;
  • an explicit security descriptor, never default pipe security;
  • FILE_FLAG_FIRST_PIPE_INSTANCE with a cryptographically random per-run pipe name;
  • DACL permitting only Account A, Account B, and explicitly selected operator or service identities;
  • no Everyone ACE and no anonymous-access ACE;
  • explicit denial of NT AUTHORITY\NETWORK and of anonymous access;
  • a broker-owned rendezvous artifact carrying the pipe name and expected broker identity;
  • shim-side verification of both the pipe-server process and its expected account before any proposed_bytes are transmitted;
  • a negative test demonstrating that a remote-style client is rejected.

Server verification matters for confidentiality even though the shim holds no mutation authority: a squatter winning the pipe name would otherwise receive proposed file contents.

Each slice requires its own approval. None is authorized by this document.

🤖 Generated with Claude Code

Requirements proposal only. Implementation authority: none. No code, tests,
schema, CLI, runtime integration, IPC, file access, OpenClaw installation, or
capability behavior is changed.

CR-OC-001A defines how one exact single-file content transition is
represented, validated, and bound, as the first of five slices in the mediated
OpenClaw experiment. The proposed module is pure: it reads no file, writes no
file, opens no connection, and touches no database.

Contract highlights:

- target_file_id is the tool-facing identifier; a caller-supplied path is
  never accepted where a file ID belongs, and canonical_relpath is carried for
  evidence legibility only, never as an input channel.
- proposed_bytes is transient. It is verified against expected_post_digest and
  then never digested directly, never projected, and never persisted. Every
  digest that carries the post-digest binds the content transitively without
  handling it.
- declared_context_digest and broker_connection_id remain structurally
  separate in the effect, the projection, and the API.
- The authorized effect maps onto the merged CR-YK-002 capability without
  changing AuthorizationRequest or the capability schema.
- Replay semantics are keyed to the client request, not merely the capability:
  at most one claim per client request.
- The closed validation vocabulary covers only conditions this module can
  actually detect. Path traversal, symlink handling, broker availability,
  capability lifecycle, and execution outcomes are deliberately absent,
  because a code for an undetectable condition is a false capability claim in
  vocabulary form.

Three honesty boundaries are recorded rather than left for a reviewer to
notice:

- A pure module cannot distinguish a broker-minted broker_connection_id from a
  forged one. This slice establishes only that it is a separate field with
  separate provenance participating in the effect digest. Trustworthiness is
  entirely a CR-OC-001D property.
- Digesting client-declared invocation context makes it tamper-evident after
  the fact. It does not make it true when first supplied, and nothing here
  authenticates the runtime, agent, or session.
- The bounded claim is that an exact authorized pre-content digest became an
  exact authorized post-content digest. That is not exact filesystem state:
  timestamps and file identity may change during replacement, and owner, DACL,
  regular-file status, and workspace containment are left to CR-OC-001C with
  their own tests.

One sequencing consequence is recorded now rather than discovered later.
scope_digest covers the whole authorized effect including broker_connection_id,
and CR-YK-002 binds scope_digest immutably at claim time. A capability is
therefore bound to one broker connection: it cannot be claimed across a
reconnect, and issuance must follow connection establishment. That is the
intended containment property but a real ordering constraint on CR-OC-001B and
CR-OC-001D.

The mutant table names a designated killer test for each mutant and records
that the declared-identity mutant is weaker than the rest, since it is a naming
and structure defect rather than a behavioral one and a determined rename could
defeat its test. Reviewers, not tests, are the control there.

The named-pipe hardening redlines are recorded as CR-OC-001D requirements and
are explicitly not satisfied by any earlier slice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for poetic-quokka-0fd859 ready!

Name Link
🔨 Latest commit 0f5a2a4
🔍 Latest deploy log https://app.netlify.com/projects/poetic-quokka-0fd859/deploys/6a6883dbfd6fac000867e2cd
😎 Deploy Preview https://deploy-preview-119--poetic-quokka-0fd859.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

coreytshaffer and others added 2 commits July 28, 2026 03:19
Review of PR #119 found six internal contradictions and underspecifications
that documentation-only CI cannot detect. Still a requirements proposal;
implementation authority remains none.

1. proposed_bytes must be digested directly. The document claimed they are
   "never digested directly" while also requiring verification against
   expected_post_digest, which necessarily hashes the exact bytes. That claim
   was false. Replaced with a retention rule: the bytes are hashed transiently
   to verify sha256(exact_proposed_bytes) == expected_post_digest, and never
   enter canonical JSON, any digest input, the projection, or durable
   evidence. Exact-byte treatment is now binding -- UTF-8 validity is a gate
   rather than a normalization step, with no Unicode normalization, no newline
   conversion, no BOM insertion or removal, and content_size_bytes measured on
   the original sequence. Without this, two conforming implementations could
   hash different post-content from identical input.

2. The bounded claim overstated provenance. The objective said
   "broker-authenticated connection binding" and the acceptance claim said
   "broker-generated connection identifier", both contradicting the honesty
   section. All positive claims now read "a distinct broker-connection
   identifier field whose provenance and connection binding are not
   established by this slice", and broker-generated/broker-authenticated are
   reserved for CR-OC-001D. The same precision is applied to target_file_id,
   which this slice validates syntactically without establishing allowlist
   membership. A provenance table now states, per field, what is and is not
   established.

3. Request identity omitted the connection binding. request_digest was defined
   over the proposal, which does not carry broker_connection_id, while the
   effect and scope digest do -- so the same client_request_id and
   request_digest could recur unchanged after a reconnect even though the
   authorized effect had changed. triagecore.mediated_client_request.v1 now
   includes broker_connection_id, a repeat on a different connection is
   request_id_reuse_mismatch rather than idempotent replay, and the integrated
   ordering is explicit: reservation follows connection establishment, not
   only issuance.

4. The replay vocabulary contradicted statelessness. A module that stores
   nothing cannot own request_id_reuse_mismatch, because it cannot discover
   that a prior request exists. Resolved by splitting comparison from storage:
   classify_request_replay is a pure function receiving both bindings as
   arguments and returning new_request, idempotent_replay, or
   request_id_reuse_mismatch, with CR-OC-001B supplying stored values
   atomically. Also recorded that the "one capability per client request" rule
   is not representable here at all, since capability_id appears nowhere in
   this slice, and is therefore a CR-OC-001B obligation to bind capability_id
   one-to-one with client_request_id.

5. Canonical objects were undefined. "Digest of the canonical
   proposal/decision linkage" is not implementable. Five versioned objects are
   now specified field by field with closed field sets, a schema discriminator
   inside the digest input, required non-null fields (optional values are
   forbidden rather than omitted, so an omitted-key form cannot exist), and
   the existing canonical_json_bytes representation. Domain separation follows
   the house pattern from governed_decision.py -- a field inside the envelope,
   not a byte prefix. A cross-domain distinctness test is required rather than
   assumed.

6. Two downstream-boundary details. canonical_relpath was described as
   something no authorization may key off, but it is in the effect and does
   affect scope_digest; the rule is now stated correctly as integrity-bound
   for evidence consistency but never a resolution channel, with resolution
   exclusively through target_file_id. The CR-OC-001D pipe redlines were
   incomplete and now carry the full set: DACL limited to Account A, Account
   B, and explicitly selected operator identities; no Everyone or anonymous
   ACE; explicit denial of NT AUTHORITY\NETWORK and anonymous access; a
   broker-owned rendezvous artifact; shim verification of both server process
   and expected account before content is transmitted; and a negative test for
   remote-style clients.

Test contract expands from 11 to 17 items and the mutant table from six to
nine, adding killers for request-digest connection binding, byte-normalization,
and schema-discriminator removal. Backlog and change-log entries are corrected
to match; both previously repeated the false "never digested" claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Clarifies how classify_request_replay represents "no existing binding". The
prior signature took four required strings while its rules also required an
absence case, which is not expressible in that shape. Requirements proposal
only; implementation authority remains none.

The signature becomes:

    RequestBinding
        client_request_id
        request_digest

    classify_request_replay(
        existing: RequestBinding | None,
        incoming: RequestBinding,
    ) -> new_request | idempotent_replay | request_id_reuse_mismatch

None is the explicit absence sentinel. RequestBinding is a comparison input,
not a sixth canonical digest object: it is never canonicalized, digested, or
persisted, so the "optional values are forbidden, not omitted" rule does not
reach it. That rule governs the five canonical digest objects only, where an
omitted-key form must not exist because it would digest differently. Absence
here is a real state the caller must be able to express. Within a
RequestBinding both fields remain required and non-null.

The one input that cannot arise from a correct caller is now defined rather
than left open. CR-OC-001B looks up by the incoming client_request_id, so a
non-null existing binding carrying a different ID indicates a caller defect.
That input is invalid and must raise.

Raising is chosen over returning because both available return values would be
wrong in a way that matters: new_request would let a lookup bug hand back
mismatched state and obtain fresh authority, and request_id_reuse_mismatch
would report a reuse conflict that never happened. Raising surfaces the defect
where it can be fixed, and matches the existing house treatment of
caller-contract violations -- CapabilityClaimStore.claim raises
CapabilityStoreError for a blank claimant rather than inventing a reason code.

Test contract grows to 18 items, adding the raise-on-mismatched-existing-ID
case and splitting the classification assertions by branch. Tail items are
renumbered and the mutant table cross-reference is updated accordingly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coreytshaffer
coreytshaffer merged commit bfa1d6e into main Jul 28, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant