carlos / platform Public

Align resume failure and contract validation with guard transitions

Paul Campbell pushed by paul@keymail.dev 5f18cddc11da6c16b552999b5957947311014296 parent 78b56fe
2 files changed, +57 −18
  • implementation/maintenance-protocol/proposal.json +11 −8
  • implementation/maintenance-protocol/schemas.json +46 −10

CI passed — run details

diff --git a/implementation/maintenance-protocol/proposal.json b/implementation/maintenance-protocol/proposal.json
index 8925b9e79..7989abafa 100644
--- a/implementation/maintenance-protocol/proposal.json
+++ b/implementation/maintenance-protocol/proposal.json
@@ -191,9 +191,10 @@
"approval_forms": "application/x-www-form-urlencoded; exact fields in approval_form, one value each; reject unknown/duplicate fields. csrf uses existing console field and is never copied into durable approval metadata. Browser Origin/CSRF checks must match normal console/router contract; any stronger Origin rule needs explicit integration tests.",
"metadata_digest": "New proposal: deterministic Go encoding/json serialization of the declared version-1 typed object in schemas.json property order, no indentation or appended newline, normal Go HTML escaping; sorted unique required-contract arrays and sorted unique environment entry names. Compare exact stored bytes for reference digests; no float64 intermediates.",
"approval_endpoint": "The single review/approve path uses the NEW request_id for operation, retry, or resume. Server immutable request kind determines the required operation or compatibility/resume checks. Predecessor is metadata, never substituted into the approval path. Missing/mismatched kind/pins refuses.",
- "browser_scope": "Use existing accountApp sqid routing and resolve sqid to exact account before comparing stored scope; sqid is not an account slug. Approval redirects 303 to the same new request review URL /a/{sqid}/apps/{app}/instances/{host}/maintenance/{request_id}/review. Owner and fresh-auth gate order is unchanged.",
+ "browser_scope": "Use existing accountApp sqid routing and resolve sqid to exact account before comparing stored scope; sqid is not an account slug. Approval redirects 303 to the same new request review URL /a/{sqid}/apps/{app}/instances/{host}/maintenance/{request_id}/review. Owner and fresh-auth gate order is unchanged. Existing resolver accepts an unambiguous account name as well as sqid; emitted URLs use canonical sqid. accountApp alone is member-readable and insufficient: maintenance handlers must perform the explicit owner gate with identical missing/nonowner HTTP404 before fresh-auth, including missing-app response normalization.",
"predecessor_agreement": "Initial POST /requests requires predecessor=null. Retry and resume require non-null predecessor; predecessor.id equals path request_id and its digest equals that exact predecessor request reference (not an outcome ID). Server resolves that request to its immutable authoritative outcome and current lineage head; every body selector must agree. Missing outcome, stale predecessor or mismatch refuses. New body request_id is distinct from predecessor/path request_id.",
- "result_semantics": "GET /result resolves status.outcome from authoritative current request metadata. Pending resume, including compatibility-checked/resume-ready, returns409 result-unavailable; it never returns the intermediate checker receipt as resumed. Before a final outcome status.result_state=pending and result_envelope_digest=null; historical compatibility/evidence refs may be non-null. For final resume outcome, return that final outcome/ref and exact retained checker envelope whose digest is copied unchanged from compatibility_outcome; server verifies both immutable receipts and linkage under the same request/attempt/scope/recipient. No checker output yet or lost output yields409 unavailable; expired private payload yields410. Final failed/ambiguous resume may expose an existing validated checker result but state remains failed/ambiguous and CLI must not report resumed. Operation result semantics remain exact command ciphertext under its final outcome. Result envelope alone never establishes outcome or admission."
+ "result_semantics": "GET /result resolves status.outcome from authoritative current request metadata. Pending resume, including compatibility-checked/resume-ready, returns409 result-unavailable; it never returns the intermediate checker receipt as resumed. Before a final outcome status.result_state=pending and result_envelope_digest=null; historical compatibility/evidence refs may be non-null. For final resume outcome, return that final outcome/ref and exact retained checker envelope whose digest is copied unchanged from compatibility_outcome; server verifies both immutable receipts and linkage under the same request/attempt/scope/recipient. No checker output yet or lost output yields409 unavailable; expired private payload yields410. Final failed/ambiguous resume may expose an existing validated checker result but state remains failed/ambiguous and CLI must not report resumed. Operation result semantics remain exact command ciphertext under its final outcome. Result envelope alone never establishes outcome or admission. After final outcome status.result_state=available with non-null digest while payload exists; expiry sets expired and preserves immutable digest/ref, loss sets unavailable without inventing payload. Schema does not force available forever for resumed because private ciphertext expires after24h; resumed admission history persists.",
+ "required_contracts": "Resume requires1..16 sorted unique names, each matching maintenanceguard.ValidContract lowercase grammar; reject unsorted input without silently normalizing signed request bytes. Require a superset of retained guard contracts before preparing/approving or starting a process. Empty/invalid/duplicate/unsorted/floor-removing sets refuse before any spawn."
},
"cli": [
{
@@ -262,7 +263,7 @@
"execution_disconnect": "HTTP client disconnect does not cancel durable worker ownership. Worker timeout/lease loss invokes checked stop and publishes failed/ambiguous when possible; never automatically retries.",
"retry": "New platform request linked to immutable predecessor receipt. Same exact app stdin/argv required: client validates retained private hash; worker independently compares decrypted predecessor input with new input while private predecessor payload exists. Expired/missing predecessor payload refuses retry; operator may prepare a separately reviewed recovery request preserving Home idempotency, never rewrite old authority. No fresh Home signature is generated by carlos.",
"concurrency": "One exact active request per retained lifecycle ownership. Concurrent retries may prepare but only approved matching claim can execute; no last-writer-wins head. Existing incomplete lifecycle claim must be recovered explicitly rather than stolen.",
- "resume": "Original operation may have succeeded, failed, or become ambiguous; resume eligibility requires checked stop/containment and a NEW independently successful app-specific non-serving compatibility-check command from the pinned serving release, under the retained guard/lifecycle/lease. Private checker argv/stdin use unchanged CMI1+DirectionInput; stdout uses CaptureResult+SealResult to the newly durable per-request CLI key. Approval pins exact checker ciphertext, artifact/manifest, environment snapshot, record identity/revision, predecessor and required contracts. Validate checker exit plus app-specific compatibility result, then integrity and final checkpoint/restore evidence. Publish immutable resume-ready evidence while guard remains in the existing ResumeRequested phase; do not invent a new guard enum. Only then start exact serving artifact and hand lease to replication, verify health/version, and CAS matching resumed guard+projection. Failure keeps serving admission closed. Migration success is not compatibility proof; checker cannot rerun the original mutation.",
+ "resume": "Original operation may have succeeded, failed, or become ambiguous; resume eligibility requires checked stop/containment and a NEW independently successful app-specific non-serving compatibility-check command from the pinned serving release, under the retained guard/lifecycle/lease. Private checker argv/stdin use unchanged CMI1+DirectionInput; stdout uses CaptureResult+SealResult to the newly durable per-request CLI key. Approval pins exact checker ciphertext, artifact/manifest, environment snapshot, record identity/revision, predecessor and required contracts. Validate checker exit plus app-specific compatibility result, then integrity and final checkpoint/restore evidence. Publish immutable resume-ready evidence while guard remains in the existing ResumeRequested phase; do not invent a new guard enum. Only then start exact serving artifact and hand lease to replication, verify health/version, and CAS matching resumed guard+projection. Failure keeps serving admission closed. Migration success is not compatibility proof; checker cannot rerun the original mutation. On final checker/handoff failure use existing ResumeRequested -> Failed/Ambiguous transition with same request/attempt/pins; only a new explicitly approved attempt may then resume from that phase.",
"expiry": "Server immutable payload expiry24h from first accepted upload; idempotent resend never extends it. Proposed preflight review expiry5min; expiry requires fresh preflight/new candidate and explicit review, never silently renew old consent. Execution must begin before candidate expiry; child and operation deadlines independently bounded."
},
"Home_integration": {
@@ -316,7 +317,9 @@
"Browser sqid resolves through accountApp; mismatched stored account refuses before fresh-auth and 303 returns exact review route.",
"Changing lifecycle OperationID, expected head revision or predecessor outcome invalidates candidate_digest and held-POST approval; no latest-head substitution.",
"Retry null predecessor and retry/resume predecessor differing from path reject before draft creation; predecessor digest means request reference, not outcome reference.",
- "Resume-ready has no final outcome and GET result returns409; final resumed outcome links immutable checker outcome/evidence and returns unchanged checker envelope. Failed handoff never reports resumed or clears guard."
+ "Resume-ready has no final outcome and GET result returns409; final resumed outcome links immutable checker outcome/evidence and returns unchanged checker envelope. Failed handoff never reports resumed or clears guard.",
+ "Failed checker publishes final failed/ambiguous outcome with null compatibility/evidence references and transitions ResumeRequested to Failed/Ambiguous; fresh new resume from that phase succeeds only with all new approval/execution checks.",
+ "Empty/invalid/duplicate/unsorted required contracts and attempts to remove retained contracts refuse before any process starts."
],
"lifecycle_lineage": {
"stable_identity": "Server derives lifecycle_operation_id from the retained trusted initial request anchor (proposed equal to initial platform request_id) and existing Guard.OperationID/lifecycle claim. New retry/resume request_id and attempt_id never create a new app claim or replace OperationID before Resumed. Caller has no lifecycle-operation-ID write field.",
@@ -328,18 +331,18 @@
"input": "Exactly maintenance.Input{Arguments:CHECK_ARG,Stdin:private JSON}, EncodeInput/SealInput to approved deployment key with new request/attempt and DirectionInput. Worker supplies executable and database path; no caller environment or fd/path authority. Generic limits remain 32args/8192 aggregate/65536 stdin.",
"output": "Exact <=65536 bytes on dedicated stdout, captured to EOF and encrypted with existing SealResult/DirectionResult to new request recipient. App-specific checker JSON/schema and compatibility validation belong to reviewed serving artifact contract. Platform does not accept an arbitrary compatible=true body or a caller-uploaded checker receipt.",
"authority": "Approved serving artifact must actually expose the intended non-serving checker; no guessed Home command or unmounted dispatcher is treated as implemented. Worker executes/checks it under the same fence/lease and authenticated provenance as mutations; independent integrity/checkpoint follows success.",
- "publication": "Checker success publishes immutable compatibility-checked outcome with execution_kind=compatibility-check and private checker result. status.compatibility_outcome retains that reference; status.outcome stays null while final resume is pending. Resume-ready evidence links that exact outcome, immutable approval candidate, final checkpoint/restore and exact serving pins. status.resume_ready_evidence is set only after validated publication, with guard still ResumeRequested. After handoff, exact health/version and matched resumed guard/projection, publish a NEW immutable outcome state=resumed, execution_kind=resume, referencing compatibility_outcome and resume_ready_evidence; CAS status.outcome to it. No receipt is overwritten. Final result_envelope_digest is the SAME checker ciphertext digest, encrypted under the same resume request/attempt/key; no second command/output or re-encryption. Historical compatibility receipt stays reachable from final outcome. Failure after checker success may publish final failed/ambiguous resume outcome with existing compatibility/evidence links but never resumed; serving stays fenced.",
+ "publication": "Checker success publishes immutable compatibility-checked outcome with execution_kind=compatibility-check and private checker result. status.compatibility_outcome retains that reference; status.outcome stays null while final resume is pending. Resume-ready evidence links that exact outcome, immutable approval candidate, final checkpoint/restore and exact serving pins. status.resume_ready_evidence is set only after validated publication, with guard still ResumeRequested. After handoff, exact health/version and matched resumed guard/projection, publish a NEW immutable outcome state=resumed, execution_kind=resume, referencing compatibility_outcome and resume_ready_evidence; CAS status.outcome to it. No receipt is overwritten. Final result_envelope_digest is the SAME checker ciphertext digest, encrypted under the same resume request/attempt/key; no second command/output or re-encryption. Historical compatibility receipt stays reachable from final outcome. Failure after checker success may publish final failed/ambiguous resume outcome with existing compatibility/evidence links but never resumed; serving stays fenced. A checker failure publishes final failed/ambiguous execution_kind=compatibility-check outcome in status.outcome, with compatibility_outcome=null and resume_ready_evidence=null; only successful checker validation produces compatibility_outcome. If bounded output was captured and authenticated it may be fetched with the failure outcome; absent output is unavailable, never fabricated. Either checker or later handoff failure advances the matching ResumeRequested guard to Failed/Ambiguous with unchanged request/attempt/pins and next generation, preserving denial and allowing a separately freshly approved new resume. Failed CAS/publication leaves recovery-required uncertainty, never automatic execution or serving.",
"expiry": "Fresh approval and exact checker input expiry are checked independently. No retry/resume extends prior Home authorization, payload expiry, worker lease or operation timeout."
},
- "revision_note": "R2 addresses Fable seven blockers on bae085e; now based on merged11a3039 with exact primitive sources included. Interface-only proposal; no runtime implementation, worker authority or deployment approval.",
+ "revision_note": "R3 addresses two new R2 blockers against guard.validAdvance/ValidContracts, plus routing/reference syntax clarifications. R1 seven blockers remain resolved. No implementation or deployment approval.",
"status_and_guard_mapping": {
"authority": "Status is per request workflow metadata, never serving admission authority. Guard phase plus matching projection remains authoritative. Draft retry/resume does not change the predecessor guard. The mapping below distinguishes workflow overlays and guard phases; do not infer absence or clear a guard from draft/refusal/expiry.",
"awaiting-preflight, awaiting-approval": "No new guard transition. Initial request has no fence; retry/resume retains the current predecessor guard and denial.",
"approved": "Owner gesture accepted, before durable requested fence (initial/retry) or ResumeRequested transition (resume); no command may launch from this status. Existing fence remains intact.",
- "requested, quiescing, fenced, executing, succeeded, failed, ambiguous": "Operation workflow states map to identical guard phases after matching durable references. Resume checker execution/failure is instead a substate under ResumeRequested: guard must not move backward to Executing/Failed. State alone never authorizes serving.",
+ "requested, quiescing, fenced, executing, succeeded, failed, ambiguous": "Operation workflow states map to identical guard phases after matching durable references. Resume checker execution is a substate under ResumeRequested, not a backward move to Executing. Final failed/ambiguous checker or handoff advances ResumeRequested to Failed/Ambiguous with the same request/attempt/pins and next generation, exactly guard.validAdvance. These phases remain denied; subsequent fresh-owner resume uses new request/attempt from Failed/Ambiguous with stable OperationID.",
"resume-requested": "Guard ResumeRequested after fresh resume approval and required CAS; all checker, validation and serving handoff steps remain denied.",
"resume-ready": "Workflow substate only; guard remains ResumeRequested. Immutable checker outcome and resume-ready evidence exist but no resumed claim.",
"resumed": "Only after exact serving health/version, lease handoff, and both matching guard Resumed and record projection. No premature success pointer.",
- "refused, expired": "Pre-execution workflow refusal/expiry creates no fence and clears none. A previous active guard remains unchanged. After fence acceptance approval expiry cannot undo it; command/lease failures map failed/ambiguous for operations or stay ResumeRequested for resume."
+ "refused, expired": "Pre-execution refusal/expiry creates no fence and clears none. Previous active guard remains unchanged. After fence acceptance expiry cannot undo it. Command/lease/checker/handoff failure publishes failed/ambiguous with checked stop evidence and CASes the matching guard to Failed/Ambiguous; uncertainty keeps admission closed. No timeout takeover or automatic retry."
}
}
diff --git a/implementation/maintenance-protocol/schemas.json b/implementation/maintenance-protocol/schemas.json
index d7b2e62d3..a2af21654 100644
--- a/implementation/maintenance-protocol/schemas.json
+++ b/implementation/maintenance-protocol/schemas.json
@@ -105,7 +105,11 @@
"revision": {
"type": "string",
"minLength": 1,
- "maxLength": 256
+ "maxLength": 256,
+ "pattern": "^[!-~]+$",
+ "not": {
+ "pattern": "\\*"
+ }
}
}
},
@@ -139,12 +143,23 @@
"record_revision": {
"type": "string",
"minLength": 1,
- "maxLength": 256
+ "maxLength": 256,
+ "pattern": "^[!-~]+$",
+ "not": {
+ "pattern": "\\*"
+ }
},
"artifact_version": {
"type": "string",
"minLength": 1,
- "maxLength": 64
+ "maxLength": 64,
+ "pattern": "^[a-zA-Z0-9._-]{1,64}$",
+ "not": {
+ "enum": [
+ ".",
+ ".."
+ ]
+ }
},
"artifact_digest": {
"$ref": "#/$defs/digest"
@@ -317,12 +332,23 @@
"record_revision": {
"type": "string",
"minLength": 1,
- "maxLength": 256
+ "maxLength": 256,
+ "pattern": "^[!-~]+$",
+ "not": {
+ "pattern": "\\*"
+ }
},
"artifact_version": {
"type": "string",
"minLength": 1,
- "maxLength": 64
+ "maxLength": 64,
+ "pattern": "^[a-zA-Z0-9._-]{1,64}$",
+ "not": {
+ "enum": [
+ ".",
+ ".."
+ ]
+ }
},
"artifact_digest": {
"$ref": "#/$defs/digest"
@@ -529,7 +555,8 @@
{
"type": "null"
}
- ]
+ ],
+ "description": "Opaque server-internal immutable reference; exact stored encoding and authenticated producer validation belong to future coordinator; CLI comparison is not provenance proof."
}
},
"allOf": [
@@ -642,7 +669,11 @@
"record_revision": {
"type": "string",
"minLength": 1,
- "maxLength": 256
+ "maxLength": 256,
+ "pattern": "^[!-~]+$",
+ "not": {
+ "pattern": "\\*"
+ }
},
"artifact_digest": {
"$ref": "#/$defs/digest"
@@ -718,7 +749,8 @@
{
"type": "null"
}
- ]
+ ],
+ "description": "Opaque server-internal immutable reference; exact stored encoding and authenticated producer validation belong to future coordinator; CLI comparison is not provenance proof."
}
},
"allOf": [
@@ -869,8 +901,12 @@
"items": {
"type": "string",
"minLength": 1,
- "maxLength": 64
- }
+ "maxLength": 64,
+ "pattern": "^[a-z][a-z0-9]*([.-][a-z0-9]+)*$"
+ },
+ "minItems": 1,
+ "uniqueItems": true,
+ "x-validation": "maintenanceguard.ValidContracts; strict ascending byte order and superset of retained contracts before approval/spawn."
},
"result_recipient": {
"$ref": "#/$defs/recipient"