diff --git a/CHANGELOG.md b/CHANGELOG.md index 48bfccd82..fb0849e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Add a Noema-owned exact-claim evidence receipt contract whose execution and research producers serialize one canonical artifact that binds every receipt semantic field, including command/result/isolation/network or source revision/excerpt/retrieval policy. Admission accepts only a receipt ID from untrusted model output. The owner API first verifies the exact authenticated OpenCode-handoff manifest digest, canonical envelope bytes, reviewed producer-to-kind policy, and repository/head/workflow/run/attempt identity before it can construct an immutable typed index; admission then reconstructs each canonical artifact and verifies time/claim/artifact identity. The version-2 manifest now binds a separate producer-authenticated `ClaimEvidenceRequirement` containing the exact claim, independently required evidence kind, and `context` or `finding` publication authority. Raw current-head source lines are context only: they are withheld from finding-reference prompts and cannot publish a finding or `request_changes`; an explicitly producer-authorized source finding remains usable and retains exact path/line checks. Finding-free model `request_changes` and `blocked` verdicts cannot bypass receipt admission to publish a vacuous blocking review. Requirement/receipt kind mismatch, fixed-artifact semantic substitution, caller-supplied receipt dictionaries, model self-classification, stale identities, cross-kind receipts, marker-only sandbox output, noncanonical artifact bytes, and expired receipts fail closed before the GitHub publisher. This remains the owner prerequisite for ContextualWisdomLab/.github#1641 and issue #555. The reviewed `sandboxed_verify` adapter exists in owner source, but its actual central stdout/stderr/marker-to-manifest wiring and the trusted research producer are not yet integrated; exact-head hosted GREEN, immutable release, and the verified central consumer bump remain required. ## Unreleased +- Protected #704 exact `42b15e865bdf88fde622c3bba2c0b123770d18be`, integrated by GitHub-verified normal merge `e8c2002e5af8fa5611880091dab81916bb716c35`, requires Agent Runtime to admit private Workflow / Task Execution current-state evidence only when a successful response declares the owner-emitted JSON media type application/json with optional `charset=utf-8` parameter; other media-type parameters are rejected. Missing or non-JSON media types fail closed with the stable `invalid_workflow_state_response` classification before bounded parsing. Rejected-media body cancellation is best-effort cleanup: synchronous throw, asynchronous rejection, null body, or non-settling cleanup cannot replace or delay the fail-closed decision. The existing fixed 1 MiB retained buffer, 10-second absolute read deadline, fatal UTF-8/JSON admission, canonical execution binding, and reader-lock lifecycle remain unchanged. Workflow / Task lifecycle truth, provider/model routing, destination/outbound policy, credential authority, quarantine/security, Policy / Approval, State / Checkpoint, release/deployment, and foreign-domain authority remain with their existing owners; protected source/checks/merge are not an immutable release, deployed Durable Object recovery, or production p95/heap evidence. - Protected #702 exact `2b31805eb2b81b6078fefe1ebcce8007b6ca5169`, integrated by GitHub-verified normal merge `cf7f4279dbf83213557e987f0ee8fb52b12fcccf`, behaviorally verifies Noema's shared Cloudflare deploy/status/recovery request boundary rather than relying on source-text header presence. The three direct clients use `requestCloudflareJson`; executable tests observe the actual `fetch` request, require `Accept: application/json` and the owner bearer to override conflicting caller values, preserve unrelated caller headers such as `Content-Type`, enforce the existing 120-second request deadline, and continue into the 1 MiB bounded JSON reader. `Accept` remains request-side representation preference rather than provider response-shape authority. Cloudflare/provider truth, destination/outbound policy, credential authority, quarantine/security, recovery-state, release/deployment and foreign-domain authority remain with their existing owners; protected source/test/CI evidence is not an immutable release, production deployment, controlled recovery rehearsal, deployed p95/heap or production KPI evidence. - PR #701 source exact `80d3d579ad4a08ccb3f21e2735c7fb0bebc7b492` makes Noema's direct Cloudflare deploy/status/recovery control-plane clients explicitly negotiate machine-readable Cloudflare-generated errors with `Accept: application/json` before their existing 1 MiB bounded JSON response reader. Cloudflare's 2026-05-05 error-response contract documents HTML as the default for generated errors when `Accept` is absent or `*/*`, and structured JSON when `application/json` is requested; Noema still treats negotiation as preference rather than shape authority and retains fail-closed fatal UTF-8/JSON/provider-error admission. Existing 120-second request bound, fixed retained buffer, reader/cancellation lifecycle, exact recovery-state checks, credential capability, destination/outbound policy, Cloudflare/provider authority and release/deployment authority remain unchanged. This source PR is not an immutable release, production deployment, controlled recovery rehearsal, or deployed p95/heap evidence. - Protected #699 bounds Agent Runtime's private Workflow / Task Execution current-state response read at exact source `e52408745efc73351c098227ae4c85c309fdd0c9` with a 10-second absolute read deadline. Deadline expiry preserves the stable `invalid_workflow_state_response` fail-closed contract, requests reader cancellation only as best-effort cleanup, clears the deadline timer, and releases the reader lock on every terminal path. The existing fixed 1 MiB retained buffer, byte ceiling, fatal UTF-8/JSON admission, canonical execution binding, and Workflow / Task Execution ownership remain unchanged. Workflow / Task lifecycle, provider/model routing, outbound, credential, quarantine/security, release/deployment, and foreign-domain authority stay with their existing owners; this protected source is not an immutable release, deployed Durable Object recovery, or production p95/heap evidence. PR #699. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 9b7e4e998..348167818 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -154,3 +154,11 @@ The existing 65,536-byte ceiling, optional caller-supplied deadline with no inve Protected history includes merged PR #699 exact `e52408745efc73351c098227ae4c85c309fdd0c9`. Protected #699 closes the stalled-read liveness gap on Agent Runtime's private Workflow / Task Execution current-state response path: after successful reader acquisition, a 10-second absolute read deadline prevents an HTTP 200 response that never yields another chunk from pinning procedural guidance indefinitely. Deadline expiry preserves the stable `invalid_workflow_state_response` fail-closed contract, requests cancellation only as best-effort cleanup, clears the deadline timer, and releases the reader lock on every terminal path. The existing fixed 1 MiB retained buffer, byte ceiling, fatal UTF-8/JSON admission, canonical execution binding, and Workflow / Task Execution ownership remain unchanged. #699 remains protected source/test evidence; immutable release, deployed Durable Object recovery, and production p95/heap evidence remain separate. Workflow / Task lifecycle, provider/model routing, outbound, credential, quarantine/security, release/deployment, and foreign-domain authority stay with their existing owners. + +## Protected procedural current-lifecycle media admission — merged PR #704 + +Protected history includes merged PR #704 exact `42b15e865bdf88fde622c3bba2c0b123770d18be`, integrated by GitHub-verified normal merge `e8c2002e5af8fa5611880091dab81916bb716c35`. Protected #704 closes the representation-admission gap on Agent Runtime's private Workflow / Task Execution current-state response path: successful owner evidence must identify its representation as application/json with optional `charset=utf-8` parameter; other media-type parameters are rejected before Noema performs bounded procedural-state parsing. Missing or non-JSON media types fail closed with the stable `invalid_workflow_state_response` classification. + +Rejected-media response-body cancellation is best-effort cleanup only: synchronous cleanup throw, asynchronous rejection, null body, or non-settling cancellation cannot replace or delay the fail-closed decision. The existing fixed 1 MiB retained buffer, byte ceiling, 10-second absolute read deadline, fatal UTF-8/JSON admission, canonical execution binding, and reader-lock lifecycle after successful acquisition remain unchanged. + +#704 remains protected source/test evidence; immutable release, deployed Durable Object recovery, and production p95/heap evidence remain separate. Workflow / Task lifecycle truth stays with its Durable Object owner. #704 does not transfer provider/model routing, destination/outbound policy, credential authority, quarantine/security, Policy / Approval, State / Checkpoint, release/deployment, or foreign-domain authority into Agent Runtime. diff --git a/test/procedural-current-lifecycle-media-authority.test.ts b/test/procedural-current-lifecycle-media-authority.test.ts new file mode 100644 index 000000000..3ae1e4d7a --- /dev/null +++ b/test/procedural-current-lifecycle-media-authority.test.ts @@ -0,0 +1,92 @@ +import { readFileSync } from "node:fs"; + +import { describe, expect, it } from "vitest"; + +const CHANGELOG_704_MARKER = "- Protected #704 "; +const BASELINE_704_HEADING = + "## Protected procedural current-lifecycle media admission — merged PR #704"; + +const REQUIRED_704_CHANGELOG_FRAGMENTS = [ + "Protected #704", + "42b15e865bdf88fde622c3bba2c0b123770d18be", + "e8c2002e5af8fa5611880091dab81916bb716c35", + "application/json with optional `charset=utf-8` parameter", + "invalid_workflow_state_response", + "best-effort cleanup", +] as const; + +const REQUIRED_704_BASELINE_FRAGMENTS = [ + "merged PR #704 exact `42b15e865bdf88fde622c3bba2c0b123770d18be`", + "GitHub-verified normal merge `e8c2002e5af8fa5611880091dab81916bb716c35`", + "application/json with optional `charset=utf-8` parameter", + "stable `invalid_workflow_state_response`", + "best-effort cleanup", + "immutable release", +] as const; + +function extractChangelog704Entry(changelog: string): string { + const start = changelog.indexOf(CHANGELOG_704_MARKER); + if (start < 0) { + return ""; + } + const nextEntry = changelog.indexOf("\n- ", start + CHANGELOG_704_MARKER.length); + return changelog.slice(start, nextEntry < 0 ? changelog.length : nextEntry); +} + +function extractBaseline704Section(baseline: string): string { + const start = baseline.indexOf(BASELINE_704_HEADING); + if (start < 0) { + return ""; + } + const nextSection = baseline.indexOf("\n## ", start + BASELINE_704_HEADING.length); + return baseline.slice(start, nextSection < 0 ? baseline.length : nextSection); +} + +function hasProtected704ChangelogAuthority(changelog: string): boolean { + const entry = extractChangelog704Entry(changelog); + return REQUIRED_704_CHANGELOG_FRAGMENTS.every((fragment) => entry.includes(fragment)); +} + +function hasProtected704BaselineAuthority(baseline: string): boolean { + const section = extractBaseline704Section(baseline); + return REQUIRED_704_BASELINE_FRAGMENTS.every((fragment) => section.includes(fragment)); +} + +describe("protected procedural current-lifecycle media documentation authority", () => { + it("rejects authority fragments that are only present in neighboring documentation", () => { + const relocatedChangelogAuthority = [ + "- Protected #704 exact `42b15e865bdf88fde622c3bba2c0b123770d18be`.", + "- Protected #702 exact `2b31805eb2b81b6078fefe1ebcce8007b6ca5169`, integrated by GitHub-verified normal merge `e8c2002e5af8fa5611880091dab81916bb716c35`, requires application/json with optional `charset=utf-8` parameter and preserves invalid_workflow_state_response with best-effort cleanup.", + ].join("\n"); + const relocatedBaselineAuthority = [ + `${BASELINE_704_HEADING}\n\nProtected history includes merged PR #704 exact \`42b15e865bdf88fde622c3bba2c0b123770d18be\`.`, + "## Protected neighboring authority", + "GitHub-verified normal merge `e8c2002e5af8fa5611880091dab81916bb716c35` uses application/json with optional `charset=utf-8` parameter, stable `invalid_workflow_state_response`, best-effort cleanup, and remains separate from immutable release evidence.", + ].join("\n\n"); + + expect(hasProtected704ChangelogAuthority(relocatedChangelogAuthority)).toBe(false); + expect(hasProtected704BaselineAuthority(relocatedBaselineAuthority)).toBe(false); + }); + + it("records protected #704 without promoting Workflow / Task or production authority", () => { + const baseline = readFileSync("docs/product-technical-gap-baseline.md", "utf8"); + const changelog = readFileSync("CHANGELOG.md", "utf8"); + const source = readFileSync("src/agent-runtime/procedural-current-lifecycle.ts", "utf8"); + const changelog704 = extractChangelog704Entry(changelog); + const baseline704 = extractBaseline704Section(baseline); + + expect(changelog704).not.toBe(""); + for (const fragment of REQUIRED_704_CHANGELOG_FRAGMENTS) { + expect(changelog704).toContain(fragment); + } + + expect(baseline704).not.toBe(""); + for (const fragment of REQUIRED_704_BASELINE_FRAGMENTS) { + expect(baseline704).toContain(fragment); + } + expect(baseline704).not.toContain("#704 transfers Workflow / Task lifecycle authority to Agent Runtime"); + + expect(source).toContain("function isJsonMediaType(value: string | null): boolean"); + expect(source).toContain("Noema current workflow-state response used an unsupported media type"); + }); +});