Skip to content

Operator Task Policy Foundation

Issue2104 adds a pure Core policy and tests. It is live: the durable task store runs every transition through it, and the signed propose, confirm, status and release task routes call that store. The policy itself stays pure, with no I/O.

The reward-e-website-link-v1 version1 intent changes only the Reward-E card link in dashecorp/dashe-website website/index.html. Its 274-byte ASCII JSON fixture and SHA256 are pinned in tests. It grants task submission only, not production release consent.

It is a registry entry, not a constant (#2257, toward #2238). OperatorTaskTemplates.ById(id) / .ById(id, version) resolves a template, exact and ordinal, and returns null for an id or version nobody registered — so such a request can never borrow this one's intent. OperatorTaskPolicy.Valid(), which every store transition passes through, validates a row against the entry registered for its own TemplateId at its own TemplateVersion. The registry now holds three entries: v1 and the sandbox toggle pair (sandbox-toggle-one-b-v1, sandbox-toggle-one-a-v1, rc#2279). So the guard is load-bearing, and a row is valid only against its own entry. Every step of the pipeline resolves the task's own template: propose (#2275), the issue (#2270), MergeGate's diff rule (#2266) and the probe (#2274). The sandbox lifecycle walk (CockpitTaskLifecycleWalkE2eTests) proves it end to end.

The entry also derives the two values that used to sit beside it as literals: CanonicalIntent is built from the record's own templateId/templateVersion/repo/path/target/from/to/effectScope in the frozen key order, and IntentDigest is the sha256 of exactly those bytes. Every comparison in the feature is digest-to-digest, so a hash written by hand beside the from/to it is supposed to describe could be edited out of agreement with them and nothing would notice — the same drift class as the constant rc#2253 removed. A field that would need JSON escaping is refused, not escaped: the preimage is a frozen contract with the Cockpit Worker's mirror and check-rig-e-persona.py, so re-encoding it here would break them instead. Adding a template is therefore a data change, never a "remember to recompute the hash" change. Model text cannot change the repository, destination or operation. The digest binds content; approval separately binds the UUID request, authenticated tenant/operator/conversation and expected revision. Times are Unix epoch milliseconds; revision and time values stay within JavaScript safe integers. The owner tenant is any canonical tenant id (TenantId): in v1 every target repo is hosted in the dashecorp GitHub org, and the conductor binds the task to the tenant it resolves for that repo (tenant-0 invotek today), never to a literal. Operator/conversation IDs are 1-256 UTF-16 units, without ASCII controls or ECMAScript trim whitespace at either edge. Future revisions conflict even during replay. Raw JSON, canonical UUIDs and unknown-field rejection belong to the authenticated boundary, the signed task envelope; these typed inputs are not authentication.

State progresses Proposed -> Approved -> AttemptStarted -> Submitted or OutcomeUnknown, and a Submitted task then records its delivery once (rc#2271): MarkVerified writes the href a probe read after the merge and when, and the state stays Submitted — the stage is projected from that record instead of from the page, so a delivered task can never be re-derived into failed by a page that changes again. A replay answers Existing and never overwrites the original record, even at a stale revision, and half a record (a time with no href) is not a valid request. Conflicting scope fails before replay handling. An exact terminal replay returns the original receipt even after expiry with a stale revision. AttemptStarted and OutcomeUnknown never reopen creation. Only trusted adapter evidence of one issue with exact request/owner/attempt/repo/digest correlation can resolve uncertainty; a model URL or editable body marker is insufficient.

Applied is a proposed state transition, not permission to perform I/O. Before the issue is created, the tenant-scoped store atomically persists AttemptStarted using expected-version concurrency; the generic IEventStore append/get and the volatile idempotency store do not supply that guarantee and are not used. Conductor owns authoritative state; Worker D1 may correlate conversations only. Status recovery must never submit, and ambiguous creates must never auto-retry.

The enablement gates are built: the signed operator transport, the durable claim and recovery tests, and release consent through an enforced hold (MergeGate holds a task PR until the operator approves its exact head, and only a head Review-E approved). The existing issue pipeline can auto-execute and deploy, and normal CI/Review-E does not itself prove a per-action human release grant, which is why the hold exists. Real submission still waits for the provisioned signing key.

Issue template and live anchor

Two more pure helpers, with tests, are live. OperatorTaskIssueTemplate is the body the confirm route POSTs (C10b), its marker drives the status reconciliation and MergeGate's release hold (C9b) and audit comment, and DashecorpAnchor is what the status route's dashecorp.com probe reads (C11a, C11b).

OperatorTaskIssueTemplate (tests) generates the dashe-website issue from a registered template (#2270, see the issue comes from the template):

  • TitleOf(template) is the template's IssueCopy.Title; v1's is fix(homepage): link the Reward-E card to reward-e.com. It becomes the PR and squash title.
  • Body(template, requestId, attemptId) has the sections Change (the before and after line, derived from the template's anchors), Acceptance, Out of scope and After merge (the template's IssueCopy), then a "do not copy" line and the marker <!-- rig-e-task v1 template=<id> requestId=<uuid> attemptId=<uuid> intentDigest=<derived digest> -->. Only the two IDs vary per call. A test pins v1's text by SHA256. A template with no copy throws rather than borrowing another's words.
  • Every registered body is pasted verbatim into Dev-E's prompt, so tests prove zero hits for - [ ] or any checkbox (UmbrellaIssueGuard), Depends on, Blocked by and Part of (the dependency gate), epic (the planner validator), Closes/Fixes/Resolves in any form, and any #N reference.
  • IsTaskIssueBody is true for any <!-- rig-e-task comment, even a damaged or duplicated one, so the MergeGate release hold fails closed. TryParseMarker accepts exactly one well-formed v1 marker on its own line (LF or CRLF), naming a registered template and carrying that template's own derived digest (#2266), with canonical lowercase non-empty UUIDs. The marker is editable text: it correlates an attempt but is never evidence.

DashecorpAnchor (tests) reads served dashecorp.com HTML without a byte compare, because Cloudflare rewrites parts of the live page:

  • ExtractRewardEHref(html) returns the href of the one <a> whose class list holds both app-card and rewarde, whatever the attribute order or quoting. Anchors inside comments are ignored, and so is everything after an unterminated comment. No such anchor, more than one, or a missing href gives null.
  • The expected post-state is not here and is not a constant (rc#2253). OperatorTaskTemplate carries the template's OldAnchor, NewAnchor and AnchorClasses, and DERIVES PreviousHref and ExpectedHref from those anchors with the very parser the probe runs over the served page — so the thing verified cannot drift from the line Dev-E is asked to write. IsExpected(href) / IsPrevious(href) tolerate one trailing slash on either side. Both are derived on access, not stored: a record with copies stored fields without re-running their initialisers, so a stored expectation would survive a with that changed the anchor it came from. A test pins that ExpectedHref is the digested intent's to and PreviousHref its from.