diff --git a/.github/workflows/deploy-cloudflare-worker.yml b/.github/workflows/deploy-cloudflare-worker.yml index bc850b0..201433e 100644 --- a/.github/workflows/deploy-cloudflare-worker.yml +++ b/.github/workflows/deploy-cloudflare-worker.yml @@ -1,7 +1,7 @@ name: deploy-cloudflare-worker -# Source merges are not deployment authorisation. Keep publication rollout -# manual while the schema-first staging, drain and canary gates are outstanding. +# Source merges are not deployment authorisation. Production deployment remains +# manual and requires exact release identity plus fail-closed canary controls. on: workflow_dispatch: inputs: @@ -79,4 +79,44 @@ jobs: { mode: 0o600 } ); NODE - npx wrangler deploy --tag "${{ github.sha }}" --secrets-file "$secrets_file" + npx wrangler deploy --env="" --tag "${{ github.sha }}" --secrets-file "$secrets_file" + - name: Verify the exact fail-closed canary release + env: + EXPECTED_SHA: ${{ github.sha }} + HEALTH_URL: https://oneclickpostfactory-agent.woeinvests.workers.dev/healthz + run: | + set -eu + health_body="$(curl --fail --silent --show-error --retry 5 --retry-delay 2 --retry-all-errors "$HEALTH_URL")" + HEALTH_BODY="$health_body" EXPECTED_SHA="$EXPECTED_SHA" node <<'NODE' + const body = JSON.parse(process.env.HEALTH_BODY || "{}"); + const expectedSha = process.env.EXPECTED_SHA; + const rollout = body.rollout || {}; + if (body.ok !== true || body.liveness !== "ok") throw new Error("Worker liveness check failed"); + if (body.release?.gitSha !== expectedSha || body.release?.versionTag !== expectedSha) { + throw new Error("Deployed Worker does not match the requested Git SHA"); + } + if (body.release?.schemaContract !== "publication-ledger-v1") { + throw new Error("Deployed Worker does not advertise publication-ledger-v1"); + } + if (body.release?.schemaContracts?.workerClaims !== "worker-claims-v1") { + throw new Error("Deployed Worker does not advertise worker-claims-v1"); + } + if (rollout.tenantScope !== "allowlisted" || rollout.canaryRequired !== true) { + throw new Error("Production canary allowlisting is not active"); + } + if (!Number.isInteger(rollout.allowedTenantCount) || rollout.allowedTenantCount < 1 || rollout.allowedTenantCount > 3) { + throw new Error("Production canary tenant count is outside the 1-3 limit"); + } + if (rollout.generationEnabled !== false || rollout.providerDispatchEnabled !== false) { + throw new Error("Production generation or provider dispatch is unexpectedly enabled"); + } + console.log(JSON.stringify({ + ok: true, + workerVersionId: body.release?.workerVersionId || null, + gitSha: body.release?.gitSha || null, + tenantScope: rollout.tenantScope, + allowedTenantCount: rollout.allowedTenantCount, + generationEnabled: rollout.generationEnabled, + providerDispatchEnabled: rollout.providerDispatchEnabled, + })); + NODE diff --git a/docs/reliability-repair-progress.md b/docs/reliability-repair-progress.md index f0a0338..64d2b85 100644 --- a/docs/reliability-repair-progress.md +++ b/docs/reliability-repair-progress.md @@ -1,12 +1,12 @@ # Reliability repair progress -This file records implementation status, not production deployment or provider readiness. The final exact-SHA CI receipts are recorded on the relevant pull requests. A passing component test is not live account evidence. +This file records implementation and rollout status. Exact-SHA CI receipts remain on the relevant pull requests and deployment runs. A passing component test is not live account evidence. ## Sequence 1: release identity and complete CI gate Merged to upstream through PR #2. `npm run ci` gates typecheck, the complete normal regression suite and the compiled-runtime smoke check. Deployment invokes that same gate. Cloudflare version metadata and the Git SHA are exposed separately from provider readiness. -Hosted Threads and Instagram remain unavailable; Facebook remains paused; LinkedIn compatibility is unverified; X requires a tenant-owned connection. No production deployment is claimed here. +Hosted Threads and Instagram remain unavailable; Facebook remains paused; LinkedIn compatibility is unverified; X requires a tenant-owned connection. The bounded production canary receipt is recorded below. ## D03 containment and async runtime isolation @@ -20,17 +20,17 @@ This addresses cross-execution process-global leakage. It is not a claim that ev ## D22: explicit platform activation -Present in this branch: only exact persisted boolean `true` enables a platform. Missing rows, fields, null and false fail closed. The schema-owner bootstrap also contains default-false settings and matching UI semantics. Existing legacy true values are not mass-rewritten because their original intent cannot be inferred safely. +Merged to upstream through PR #5: only exact persisted boolean `true` enables a platform. Missing rows, fields, null and false fail closed. The schema-owner bootstrap also contains default-false settings and matching UI semantics. Existing legacy true values are not mass-rewritten because their original intent cannot be inferred safely. ## D04/D05/D06: source and angle claims and safe transport -This branch is stacked on `codex/atomic-worker-claims`, head `984d0afa1fdfd696153fa8615615b1e2d42596e3`. That dependency introduces the typed worker-claims contract, database-owned source/angle leases and fencing, atomic generation finalisation, and explicit Supabase RPC retry semantics. +Merged into upstream through the dependency chain completed by PR #9. The worker-claims layer introduces the typed worker-claims contract, database-owned source/angle leases and fencing, atomic generation finalisation, and explicit Supabase RPC retry semantics. Ordinary ambiguous mutations are single-attempt. Only reads and RPCs whose exact request identities are designed to be idempotent opt into retries. This does not claim that every agent job/enqueue path already has durable uniqueness or fencing. ## D07/D08/D09: connected publication execution -The hosted Worker consumer is now wired in `codex/publication-ledger-v1`, fork PR `AyobamiH/social-agents#4`, not merely a collection of unused helper modules. +The hosted Worker consumer is wired and merged to upstream through PR #9, not merely a collection of unused helper modules. ### Execution boundary @@ -71,8 +71,12 @@ The cross-repository integration workflow belongs to the private schema-owner re The one-time hash-guarded source-edit workflow and script were removed after committing the source delta. No write-enabled test/codemod workflow remains from this cutover. -## Production remains gated +## Production limited canary -Neither schema nor consumer is deployed by this branch. Before rollout: review the paired PRs, establish canonical schema ownership, apply to isolated staging first, verify the capability receipt, drain old publication dispatch owners, quarantine unresolved legacy publications, then test a bounded authorised cohort. Never run old and new executors as competing owners of the same queue row. Rollback must preserve ledger-owned unknown states and must not replay the legacy queue. +The schema-first rollout reached production on 9 September 2026 after the paired schema and Worker changes merged. Production migration head `20260907055000` preserves 148 queue rows while quarantining 11 members of five historical conflict groups behind immutable legacy revision holds. The persistent WSL staging rehearsal passed all 16 real Supabase/Postgres failure scenarios with provider transport intercepted and zero live posts. + +Upstream PR #9 merged as `3a27a993742151a6558089c9ab0ade6b1a762ba4`. Guarded deployment run #79 then passed complete CI and deployed Cloudflare Worker version `773bb17d-59ef-484d-ae74-2f7c004e3447` with a one-tenant allowlist, batch size one, generation disabled and provider dispatch disabled. A post-deployment production snapshot at `2026-09-09T13:10:52Z` retained all 148 queue rows and 11 held revisions, with zero active jobs, publishing rows, generation rows, publication intents or publication attempts and zero jobs touched since deployment. + +This is an inert, fail-closed production canary, not provider-readiness evidence. Production expansion remains gated on a separately reviewed configuration change, an authorised cohort and an observation receipt. Never run old and new executors as competing owners of the same queue row. Rollback must preserve ledger-owned unknown states and must not replay the legacy queue. Remaining programme work includes connection lifecycle/version fences, billing inbox and entitlements, durable generation budgets, fair scheduling and typed UI recovery, provider restoration/compatibility, and account-authorised scheduled canaries. Do not describe the whole SaaS as production-repaired based on this publication slice alone. diff --git a/src/cloudflare-worker.ts b/src/cloudflare-worker.ts index 912e07c..b9b0bb7 100644 --- a/src/cloudflare-worker.ts +++ b/src/cloudflare-worker.ts @@ -58,7 +58,8 @@ interface ExecutionContext { waitUntil(promise: Promise): void; } -const SCHEMA_CONTRACT = 'pre-publication-ledger-v1'; +const WORKER_SCHEMA_CONTRACT = 'worker-claims-v1'; +const PUBLICATION_SCHEMA_CONTRACT = 'publication-ledger-v1'; const scheduledTickGate = createExclusiveRunGate(); function canonicalGitSha(versionTag: string | undefined): string | null { @@ -121,8 +122,12 @@ function healthPayload(env: Env): Record { versionTag: metadata?.tag || null, gitSha: canonicalGitSha(metadata?.tag), versionTimestamp: metadata?.timestamp || null, - schemaContract: SCHEMA_CONTRACT, - appliedSchema: 'unverified', + schemaContract: PUBLICATION_SCHEMA_CONTRACT, + schemaContracts: { + workerClaims: WORKER_SCHEMA_CONTRACT, + publicationLedger: PUBLICATION_SCHEMA_CONTRACT, + }, + appliedSchema: 'not_evaluated', }, publicationCapabilities: publicationCapabilities(), rollout: { diff --git a/test/canary-policy.test.ts b/test/canary-policy.test.ts index f92d7f5..6847850 100644 --- a/test/canary-policy.test.ts +++ b/test/canary-policy.test.ts @@ -80,6 +80,11 @@ function main(): void { ); assert.match(workflow, /SUPABASE_WORKER_CANARY_USER_IDS/); assert.match(workflow, /--secrets-file/); + assert.match(workflow, /wrangler deploy --env=""/); + assert.match(workflow, /Verify the exact fail-closed canary release/); + assert.match(workflow, /oneclickpostfactory-agent\.woeinvests\.workers\.dev\/healthz/); + assert.match(workflow, /body\.release\?\.gitSha !== expectedSha/); + assert.match(workflow, /rollout\.providerDispatchEnabled !== false/); console.log('Canary rollout policy tests passed.'); } diff --git a/test/cloudflare-health.test.ts b/test/cloudflare-health.test.ts index fd95f3a..ee7fb87 100644 --- a/test/cloudflare-health.test.ts +++ b/test/cloudflare-health.test.ts @@ -39,8 +39,12 @@ async function main(): Promise { assert.equal(body.release.workerVersionId, 'worker-version-id'); assert.equal(body.release.versionTag, gitSha); assert.equal(body.release.gitSha, gitSha); - assert.equal(body.release.schemaContract, 'pre-publication-ledger-v1'); - assert.equal(body.release.appliedSchema, 'unverified'); + assert.equal(body.release.schemaContract, 'publication-ledger-v1'); + assert.deepEqual(body.release.schemaContracts, { + workerClaims: 'worker-claims-v1', + publicationLedger: 'publication-ledger-v1', + }); + assert.equal(body.release.appliedSchema, 'not_evaluated'); }); await test('health does not advertise disabled or unverified publishers as ready', async () => {