Skip to content

fix: GitHub OAuth handle private emails - #1

Merged
openaxcloud merged 1 commit into
mainfrom
claude/elastic-greider-b50a19
May 20, 2026
Merged

fix: GitHub OAuth handle private emails#1
openaxcloud merged 1 commit into
mainfrom
claude/elastic-greider-b50a19

Conversation

@openaxcloud

Copy link
Copy Markdown
Owner

Fallback to /user/emails API when GitHub returns null email

GitHub's /user endpoint returns email: null for accounts that mark their
primary email private (the default). resolveOAuthProfile() then threw
OAUTH_PROFILE_INCOMPLETE and the callback bounced users back to /login
with detail=OAUTH_PROFILE_INCOMPLETE even though the OAuth handshake
succeeded.

The fix falls back to /user/emails (granted by the user:email scope we
already request) and picks the primary verified address. Also adds the
User-Agent and vendor accept headers GitHub's REST API requires, and
coerces profile.id to a string so numeric GitHub IDs survive the
externalId round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 20, 2026 14:20
@openaxcloud
openaxcloud merged commit c307a73 into main May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

ℹ️ Preview deployment not configured

Name Info
Latest commit 9e48751
Status Preview deployment requires Cloudflare secrets

To enable preview deployments, repository maintainers can add:

  • CLOUDFLARE_API_TOKEN secret
  • CLOUDFLARE_ACCOUNT_ID secret

Built with ❤️ by bolt.diy

@openaxcloud
openaxcloud removed the request for review from Copilot May 20, 2026 14:44
openaxcloud pushed a commit that referenced this pull request Jun 1, 2026
…oy, add invoices endpoint (audit #1, #4, #8)

#1: call deployProviderConfigError before creating a deploy so non-static
providers without a configured hook return 400 PROVIDER_NOT_CONFIGURED
instead of a fake *.vibecore.local URL marked READY.

#4: the redeploy route now actually re-runs the build (static build +
snapshot, or provider hook) instead of cloning the previous READY row.

#8 (backend): add GET /orgs/:orgId/billing/invoices backed by a new
StripeBillingClient.listInvoices(); returns [] when no customer/Stripe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 10, 2026
…/collab-atomicity + 18 more

CRITICAL #0: SAML ACS now binds the asserted email to a domain the org has
verified ownership of (store.listDomainVerifications), blocking an org owner
from minting a self-signed assertion for an arbitrary victim (cross-org /
platform-admin account takeover).

HIGH/MED/LOW (api, CD-deployed):
- #5 platform-admin bootstrap deferred from registration to email-verify (no
  pre-verification admin squatting); /auth/reauth added to MFA-exempt paths.
- #1 redeploy route gets the create route's in-flight concurrency guard.
- #2/#19 file-search: default node_modules/.git/build ignores, file-count +
  file-size + per-line-length caps (bounds ReDoS worst-case; re2 still tracked).
- #3/#4/#17/#21 collaboration edit/terminal-perms/ai-conversation now do an
  atomic optimistic-concurrency RMW (mutateProjectIdeState) so concurrent writes
  can't clobber peers or resurrect a revoked terminal grant.
- #14/#40 git-remote SSRF: canonicalize IPv4-mapped IPv6 + decimal/hex/octal IP
  literals before the blocklist; reject embedded URL credentials.
- #18 workspace export skips dependency/build trees + caps bytes/file-count.
- #20 native WS keep-alive ping on logs/files.watch/ports.watch (LB idle flap).
- #22 Stripe webhook: don't swallow transient org-resolution errors (roll back
  dedup row + rethrow so Stripe retries); #23 resolve org via subscription
  external id so invoice.payment_failed can downgrade.
- #24 MFA setup + #47 recovery-codes now require recent re-auth.
- #25 admin workspace delete actually reclaims pod/PVC via the manager.
- #26 IP allowlist also resolves org/project from the request body.
- #27 add revoke for project share-links + chat-shares (revokedAt was dead) with
  DELETE routes; #45 WS comment.create records activity+audit like the HTTP path.
- #46 enforce snapshots.sizeMb quota; #48 last-platform-admin guard on
  revoke/suspend; #49 honest rate-limit key (drop dead currentUser ref);
  #50 enforce agent-memory opt-out; #52 dedup probe no longer inflates accessCount.

All 347 api tests pass; typecheck clean. Tests updated for the new
verify-before-admin / reauth-before-MFA / SAML-domain behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 10, 2026
…ards (TOCTOU)

Adds store.withSerializedMutation(key, fn): a Postgres transaction-scoped
advisory-lock primitive that serializes a check-then-mutate critical section
across all pods. Applied to the four TOCTOU races where a non-atomic
read-check-write let concurrent privileged requests defeat a guard:

- #1 PATCH /orgs/:orgId/memberships/:userId — owner demotion last-owner check.
- #2 DELETE /orgs/:orgId/memberships/:userId — owner removal last-owner check.
- #6 PATCH /admin/users/:userId/platform-admin — last-admin revoke check.
- #7 POST /projects/:projectId/deployments — in-flight + quota guard (two
  concurrent creates could both queue + double-consume deploy quota).

Each previously read the count, passed the >1 guard, then mutated as separate
DB round-trips, so two simultaneous requests could each pass and leave the org
owner-less / platform admin-less, or double-queue a deploy. Now the count +
mutate run under one advisory lock keyed by org/platform/project.

347 api tests pass; typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 10, 2026
7 confirmed new findings (deduped vs 191 prior across waves 14-17), all fixed:
- #0 HIGH SIEM webhook URL SSRF: siemWebhookSchema.url now refined with
  isSafeWebhookUrl (https + non-internal host, reusing the IP-literal-aware git
  host blocklist) — the stored URL is fetched server-side on abuse/delivery.
- #1 /commands/stream missing terminal-permission check: extracted a shared
  denyTerminalSocketIfRevoked() and applied it to BOTH /commands/stream and
  /terminal — a write-collaborator with terminal access revoked could previously
  run arbitrary shell via the streaming socket.
- #2 latestUserText handles parts-array message content (was sending an array as
  the agent-memory query, breaking retrieval).
- #3 web-search outbound moved to node:http(s) with a connect-time validating
  lookup (rejects any resolved internal IP at socket-connect) — closes the
  DNS-rebind TOCTOU global fetch left open; keeps per-hop redirect re-validation.
- #4 APIKeyManager getApiKeysFromCookies wraps JSON.parse in try/catch.
- #5 preview-proxy skips inspector injection for non-UTF-8 charsets (toString
  utf8 corrupted ISO-8859-1/Shift_JIS pages) — streams them byte-exact instead.
- #6 Preview 'open in new tab' uses noopener,noreferrer (untrusted preview code).

Full workspace typecheck + 351 api + 14 preview-proxy + 46 web tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 10, 2026
Deduped vs 198 prior (waves 14-18), all fixed:
- HIGH #0 proxyRuntimeSocket async message listener wrapped in try/catch — an
  unawaited rejection (malformed frame / send to closed client) is a routine
  event with no process-level handler = API crash vector.
- HIGH #1 runtime preview proxy streams the (user-controlled) response via
  Readable.fromWeb instead of arrayBuffer-buffering it whole — cross-tenant OOM.
- #2 org-suspension enforced on POST /projects/:id/deployments; #3 on AI message
  posting (suspended orgs were still queuing builds / burning LLM tokens).
- #4 proxyRuntimeSocket pendingMessages buffer capped (1000) — unbounded pre-open
  client input.
- #5 ai-gateway quorum: file-only runs (no opinion votes) no longer spuriously
  REJECTED — decide by accepted file claims + participation.
- #6 selectContext maxTokens cap (matches createSummary).
- #7 ws-manager touch() swallows P2025 on concurrent row deletion.
- #8 self-repair cookie parser tolerates malformed percent-encoding (safeDecode).
- #9 action-runner threads the action abortSignal into the self-repair LLM call.

Full workspace typecheck + local web build (client+SSR) + 351 api + 42 ai-gateway
+ 14 ws-manager tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 10, 2026
Deduped vs 208 prior (waves 14-19), all fixed (0 high — mostly hardening of
established patterns):
- #0/#1/#2/#9 fetch timeouts (AbortSignal.timeout 15s) on gitlab-branches (x2),
  github-branches (x2), gitlab-projects, github-user (x4) — hung upstream pinned
  the handler.
- #3 org-suspension enforced on deployment redeploy; #4 redeploy in-flight guard
  + #5 deploy-create quota now run inside an ORG-level withSerializedMutation
  (org-scoped quota was bypassable by concurrent creates across different projects).
- #6 ai-gateway Byzantine agreementScore excludes file votes (consistent with
  Quorum); #7 memory rate-limiter gains close() that clears the sweep interval.
- #8 ws-manager startWorkspace runs k8s cleanup BEFORE the FAILED store.update so
  a store failure can't leak the Pod/Service.
- #10 githubConnection token cookies set Secure+SameSite=strict+expiry (match useGit).

Full typecheck + local web build (client+SSR) + 351 api + 42 ai-gateway + 14
ws-manager tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 10, 2026
…(1 high)

Deduped vs 219 prior (waves 14-20):
- HIGH: GitLab SSRF IPv6-bypass — both api.gitlab-projects + api.gitlab-branches
  used a string-prefix host blocklist that let bracketed IPv6 / IPv4-mapped-IPv6
  ([::1], [::ffff:169.254.169.254], fd00::1) through while forwarding the user's
  bearer token. New shared isSafeGitForgeUrl/isBlockedHost in utils/url.ts
  (IPv6-aware via isPrivateIp + CGNAT 100.64/10); isAllowedUrl (web-search) also
  routed through it.
- agent-memory remember() quota TOCTOU: create() now enforces the per-user+scope
  cap atomically (advisory-lock tx: count+insert), so concurrent writes can't
  exceed AGENT_MEMORY_SCOPE_LIMIT; remember() maps the coded error to skipped.
- ws-manager touch() rolls back the throttle marker on a non-P2025 update failure
  (stale lastActiveAt risked premature GC).
- connector-proxy only buffers responses with a known small Content-Length
  (≤1MB); large/unknown json/text now stream (was unbounded arrayBuffer = OOM).
- netlify.ts store fetches get AbortSignal.timeout(15s).

Full typecheck + local web build (client+SSR) + 351 api + 15 connector-proxy +
14 ws-mgr tests pass.

FLAGGED for your decision (not a safe mechanical fix): wave-21 #1 — the admin SPA
is deployed (replicas=2) but has no ingress route, so it's only reachable via
kubectl port-forward. Wiring a public /admin route would expose an in-progress
admin UI; whether/where to expose it (domain, auth) is a product+security call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 11, 2026
…c rollback, MCP SSRF, token leak, project race)

- #0 synchronous POST /commands honored no terminal-revocation gate (only the two
  WS shell paths did) — an editor/member with revoked terminal access could still
  run arbitrary commands. Extracted isTerminalAccessRevoked() and applied it.
- #1 static (and github-pages/cloud-run/docker) rollback was created QUEUED but
  triggerProviderRollback has no follow-up for them, so it never reached READY and
  held a ~40min in-flight lock blocking all deploys. New providerRollbackProviders
  set = only vercel/netlify/cloudflare-pages get QUEUED; others READY immediately.
- #2/#7 isBlockedMcpUrl bypassable via IPv4-mapped IPv6 literals (URL parser folds
  127.0.0.1 to ::ffff:7f00:1, matching no dotted regex). Fold ::ffff: forms to
  dotted IPv4 and re-check the private ranges.
- #3 github-stats (unauthenticated loader) fell back to the server GITHUB_TOKEN,
  letting any anonymous caller read the platform account. Dropped the server-env
  fallback; only the caller's cookie token is used.
- #4 #persistRuntimeFilesToProjectStorage re-read the live #projectId at the
  destructive replaceExisting POST; a project switch mid-flow wrote one project's
  files into another's storage. Capture projectId+runtime once at entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 11, 2026
…visory-lock pool deadlock

- #0 (HIGH) assertRealPathContained let a DANGLING symlink at the final path
  component through: realpath() throws ENOENT on a dangling link, the loop treated
  it as 'not created yet' and approved, then writeFile/patch/restore FOLLOWED the
  link and wrote OUTSIDE the workspace root (sandbox escape). Now lstat the final
  component and reject any symlink target on the write paths (create/rename already
  immune).
- #1 (HIGH) withSerializedMutation held pg_advisory_xact_lock on a transaction
  borrowed from the MAIN pool while fn() needed a second main-pool connection. Under
  same-key burst >= poolMax, waiters' blocked transactions exhausted the pool and the
  lock holder's fn() deadlocked. Run the lock transaction on a small DEDICATED pool
  (createDatabaseClient now takes poolMax) so lock-wait connections never starve
  fn(); serialization semantics unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 11, 2026
…dings

- #0 Stripe 'incomplete'/'paused'/unknown subscription status no longer maps to
  CANCELED (which the reactivation guard then locked permanently → paid SCA/EU
  customers stuck on free). incomplete→UNPAID, paused→PAST_DUE,
  incomplete_expired→CANCELED, unknown→UNPAID (recoverable).
- #1 POST /runtime/workspaces/:id/restart now enforces workspaces.active quota
  (serialized) when bringing a STOPPED workspace back up — stop→restart could
  exceed the plan limit.
- #2 api.github-user (unauthenticated loader) drops the server GITHUB_TOKEN
  fallback — only the caller's cookie token (same as github-stats).
- #3 runtime-socket upstream 'error' listener guards client.send — an unguarded
  send on the already-closing downstream socket crashed the whole API.
- #16/#18 ws-agent /terminal onExit listener is now REMOVED on socket close
  (backend.onExit returns a disposer) — my wave-29 #3 fix leaked a listener on
  every reattach to a shared session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 11, 2026
…ert theme (UI wave 1 #1/#32/#55/#52)

- #1/#32 Markdown content p/a/inline-code break long unbroken strings
  (overflow-wrap:anywhere + word-break) on ALL chat surfaces, not just the IDE
  panel — long URLs/tokens/paths no longer overflow the column / force h-scroll.
- #55 ChatAlert error description wraps + scrolls (max-h-40) instead of stretching.
- #52 SupabaseAlert dismiss button uses theme tokens (was hardcoded dark-brown,
  broken on light theme).

Web build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 17, 2026
…) for P5

Per Avi: providers&models/updates/status/debug/logs/MCP-global + all new
(credits/usage/payg/plans/quotas/Stripe-health/users+impersonation/flags) → ADMIN.
Profile/Settings/Connections/Data/Notifications/own-MCP + NEW Billing&Usage
(plan/credit balance/effort usage/invoices = #1 user gap) → USER, surfaced
VISIBLY in the Dashboard (not the Bolt modal). Task Manager/Debug/Event Logs →
admin-only; chat API-key entry + per-user provider toggles → removed (admin
registry). Model selector stays in the IDE composer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 17, 2026
…er gap)

Adds the user-facing Credits & usage section to /billing (already in the Dashboard
Organization nav): credit balance, purchased pack balance, total available, budget
cap, and recent agent checkpoints (effort-based proof-of-work with build-tier +
high-power/extended-thinking/turbo flags). Backed by GET /orgs/:id/credits; a
credits fetch failure never breaks billing; shows a 'Preview (not charged)' badge
in shadow mode.

Verified LIVE (registered user, real API on :3001): 0 console errors, responsive
1440 + 390. User account sections already visible in the Dashboard sidebar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 20, 2026
From the exhaustive Replit Git-pane capture, two confirmed real gaps:

- Diff (#1): clicking a changed file loaded the unified diff into a raw <pre>
  dump. Replace it with GitDiffView — a parsed, line-numbered, add/remove-coloured
  renderer (old/new gutters, hunk + file-meta styling), matching Replit's diff
  view. Frontend-only; the diff text already arrives from /git/diff.
- Working tree (#2, partial): add "Stage all" / "Clear" bulk controls to the
  working-tree header (client-side staging selection, like the existing per-file
  checkboxes). Per-file/Discard-All (real git restore) is a follow-up needing a
  new /git/discard route.

Gates green: monorepo typecheck, eslint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 21, 2026
…pen time

From the sweep (double-verified), money-critical but latent (credits shadow/dormant):
- HIGH #25: debitCredits recorded the ENTIRE non-pack amount as a wallet-balance debit,
  driving the balance negative so settle's paygChargeCents (creditCents - fromPacks -
  fromBalance) was ~always 0 → PAYG AI usage was NEVER reported to Stripe and budget-cap
  / spend alerts stayed dead. Now caps the balance draw at the real wallet balance; the
  uncovered remainder overflows to PAYG. New test: 50¢ balance vs 130¢ cost → fromBalance
  clamped to 50, wallet lands at 0 (not -80), 80¢ becomes the PAYG overage. 18/18 green.
- #8: workspace reopen reset lastActiveAt but not lastMeteredAt, so the next stop metered
  the entire STOPPED window (pod deleted, zero compute) as reserved compute. Now resets
  the metering marker on reopen so only the new active window bills.

Idempotency trio (#1 storage-sweep, #7 PAYG-dedup constraint, #9 GC cross-replica) next —
each needs a dedup store-method / migration / compare-and-set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 21, 2026
…day)

The daily object-storage sweep (meterAllObjectStorage) had no dedup — unlike the
deployment meter (lastMeteredAt) and PAYG ledger (checkpointId), it recorded a usage
event + debited credits unconditionally on every invocation, so a duplicate trigger
(pod restart mid-sweep, double-schedule, manual re-run of the internal route) silently
double-charged the day's GiB-months. Add store.hasUsageEventSince(org, type, sinceMs)
and skip any org already metered for storage.objectGiBMonths since UTC midnight.
orgsMetered now reflects actually-metered orgs. Test: two runs same day → second is a
no-op, single usage event. 8/8 green.

Remaining billing: #7 (CreditLedger @@unique migration) + #9 (GC cross-replica
compare-and-set) — flagged for a careful focused pass (migration + concurrency).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 22, 2026
…stead of failing

Fan-out #1 critical (the real cold-provision root cause). The manager's /workspaces/start
blocks on pod readiness (waitForReadiness 180s + waitForAgentReachable 45s) which far
exceeds the api->manager request timeout (RUNTIME_PROXY_TIMEOUT_MS=15s). So essentially
every COLD provision threw WORKSPACE_MANAGER_UNAVAILABLE (502) — surfacing as a 'crashed
runtime' on the user's very first prompt-to-workspace step — while the manager kept
provisioning to RUNNING, leaving the api record stuck STARTING (the reconcile only ran on
a *returned* FAILED, never on a *thrown* timeout).

Now the start managerRequest is wrapped: a transient provisioning timeout / manager 5xx is
NOT treated as failure — the record stays STARTING (it genuinely is) and the route returns
a 'starting' session so the IDE adapter polls /status until RUNNING (its existing
non-running poll path), instead of a false 502. A deterministic manager error (4xx) DOES
reconcile the record to FAILED (releases the active-workspace slot, no quota lock) and
rethrows the real error.

SAFE: the success path is unchanged (warm starts that return within 15s are untouched), so
this cannot break working starts; it only converts the timeout/error path from a false 502
into the poll-to-ready flow. API typecheck clean; critical-paths/workspace api specs 7/7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 22, 2026
… (CRITICAL)

Fan-out panel-wiring #1 (critical): in remote-kubernetes (production) the editor showed
EMPTY content for every file. reloadFromRuntime() built the map from listFiles(), whose
API route (/files -> mapRuntimeNodes) and agent (/files/tree) return tree only — NO content
(content is read lazily per file via readFile). It then set content: node.content ?? '' and
HARD-REPLACED the whole map, so every file landed with ''. ProjectWorkspaceProvider hydrates
real content from project storage first, but the subsequent loadRuntimeFiles('.') reload
overwrote it with empty strings, and there is no lazy readFile on open — so opening any file
showed a blank editor. (Existing tests masked it because their listFiles mock injects content,
unlike the real mapRuntimeNodes.)

Fix: MERGE instead of clobber — when the runtime tree omits content (node.content undefined)
and the current map already holds non-empty content for that path (from project storage), keep
the existing entry. Tree structure (adds/removes/folders) still applies; local/webcontainer
mode (tree includes content) is unchanged.

New files.reload-content.spec.ts (3 tests): content preserved on tree-only reload; runtime
content used when provided; empty fallback for a brand-new tree file. Existing reload spec +
typecheck + lint green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 28, 2026
…kages)

Re-scan of all 19 packages/* (billing/quota/security/audit/rbac/auth/observability/editor/
runtime/db/...) via 7 cluster agents: 120 actionable surfaceable capabilities appended.
KEY: the Billing/Usage backend is ALREADY BUILT (routes billing.tsx + usage.tsx; endpoints
/orgs/:id/credits, /credits/limits, /billing/checkout|portal|invoices, /orgs/:id/usage) —
the #1 user gap needs SURFACING, not a new backend. Coverage now certified across app/routes
+ components + stores + services + packages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jun 30, 2026
…push (Replit parity)

Replit-parity SSH/git-in-workspace, all at the same place Replit puts git auth:

- Git Settings (⚙) → "SSH keys" section (GitSettingsPanel): generate (ed25519),
  copy public key, Test access (real `git ls-remote` in the project's isolated
  workspace pod), delete, and a "bound to origin" badge using the same host-match
  rule as push/pull key selection. Reuses the Terminal→SSH key store
  (generate-keypair / delete-ssh / git-ssh intents) so one key works in both
  places — no new backend. Mobile terminal SSH UI untouched.
- Remote bar shows an "SSH" badge when the origin speaks SSH; Pull tooltip notes
  it clones into an empty tree (verified: fresh-tree SSH pull materializes the repo).
- Shared client URL helpers in app/components/git/git-ssh-url.ts (unit-tested).

Hardening from adversarial review of the shipped SSH push path:
- #1: stop swallowing `git fetch` stderr; distinguish a missing remote branch
  (first push → create) from an auth/network failure on an existing branch
  (ls-remote guard → abort with a clear message) instead of orphaning history.
- #5: when the pod tree has no .gitignore, run `git add -A` under a transient
  core.excludesFile so node_modules/.env/build output are never swept into the
  push — without writing a .gitignore the user didn't author.

Verified in real against an OpenSSH origin: push committed only app.txt (not the
present node_modules/ or .env); pull/clone into an empty tree materialized the repo.
24 domain unit tests + 86 git-suite tests green; scoped typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jul 5, 2026
…ew is live (#1)

Live investigation of the reported session (ws-628cf) proved the BACKEND is
healthy: the pod is 1/1 Running, workspace-manager emitted workspace.running, and
API requests return 200 — the "Remote runtime request failed: 500" + stuck
"PENDING workspace" was a TRANSIENT cold-start state (the pod became ready after
the initial provisioning request timed out / returned a transient 500). 500 is
already in the retry TRANSIENT_STATUSES, so the request recovers — but the client
kept the stale workspaceError, so Problems kept showing "Error runtime … 500" and
the status stayed Error/PENDING even though the app was already serving.

Fix (client): when any forwarded port is actually serving (ready / has a URL),
clear the stale workspaceError (self-corrects — the store re-sets it if the runtime
errors again), and treat a live port as "Running" in the status label so it no
longer sticks at Error/PENDING. Pairs with the #3 chip fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jul 9, 2026
…+reseed+reinstall (#1)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jul 16, 2026
…y Profiles) · Gallery requalifiée · statut CALCULÉ (audit v4 P0-#1/#2)

P0-#1 collecteur — 3 familles (docs/launch-channel/product-route). Les
product-routes (gallery/community/pricing) sont RENDUES via navigateur
headless UA-réaliste (un fetch brut = coquille ou blocage Cloudflare).
Run réel : le collecteur RETROUVE « Community Profiles »
(manifest.watchHits.community, sha256 e9b562a2…). manifest schemaVersion 2
avec familles/watchTerms/watchHits.

P0-#2 Gallery — requalifiée sur MESURES rendues+hashées, pas déductions :
RPL-17 VERIFIED (auteurs, Views 20,650, Used 79 times, ~22 catégories, 82
Results, détail /gallery/work/…, « Submit your App » → Typeform EXTERNE =
intake humaine curée PAS self-service), RPL-18 CONFIRMED (Trust & Safety),
RPL-19 VERIFIED (Community Profiles). Sources SRC-GALLERY-RENDERED /
SRC-GALLERY-DETAIL / SRC-COMMUNITY-RENDERED (URL+hash+archive rendue).
Conséquence produit : DEC-GALLERY-NO-SELF-PUBLISH (« Publish to Gallery »
self-service = DÉPASSEMENT, pas parité — décision E-CODE avec coût modération
ou ne pas faire).

Statut CALCULÉ, jamais écrit à la main — generate-approval-status.mjs génère
APPROVAL_STATUS.json depuis les registres ; le validateur ré-exécute et ÉCHOUE
sur dérive. Nouveaux registres P0_REGISTRY (4 P0 PROVEN — pas CLOSED, pas de
reviewer humain), DECISION_REGISTRY (3), UNKNOWN_REGISTRY (4) — owner/priority/
nextAction/targetDate/expiration + schémas JSON. CI ajoute : refs croisées
surfaces↔e2e sans orphelin, freshness SLA 30j, aucune surface DONE sans
evidenceId, aucun P0 CLOSED sans commit+reviewer+preuve. 3 tests négatifs
prouvent que ces contrôles REFUSENT (dérive, CLOSED sans reviewer, id orphelin).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jul 16, 2026
…ython/go on PATH)

Agent 83459ff deployed (workspace-agent:sha-83459fff87, helm rev 859). Python
workspace reprovisioned with new agent + /nix (zone-a). With the exact PATH
sanitizedChildEnv appends, bare uv/python3/go resolve: uv 0.11.21, Python 3.12.13.
HONEST: toolchain-on-PATH is automatic (#2 solved, uv=nix-built); the nix MOUNT is
still manual-allowlist-gated (#1 auto-mount remaining, default-for-all gated on
multi-zone per OPERATIONS_DR SPOF-1). Helm pending-upgrade incident (my orphaned
rev 857) resolved via rollback 856 -> re-upgrade 859; prod stayed 200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jul 23, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jul 31, 2026
…fact brut)

Refus expert v4 (« une commande prête à jouer n'est pas une preuve exécutée »)
levé par EXÉCUTION réelle en prod le 2026-07-23, sur code intégré (merge #45 =
6d57a40, api 6d57a40) :

- POST /projects/cmrma9wof/nix-lock → 201, lock pinné gen-2 (storePath+sha256
  du catalogue signé)
- Publish #1 (gen-2 ACTIVE) → READY, URL 200, metadata storeGeneration=gen-2
- helm --set-file nixGenerations=<gen-2 REVOKED> (rev 897) + rollout api
- Publish #2 (lock gen-2 révoquée) → FAILED, refus TYPÉ
  ECODE_LOCK_GENERATION_REVOKED (« ...is REVOKED... — refusing to use it »),
  URL → 410 SERVER_DEPLOY_NOT_LIVE (aucun repli vers l'active)
- Restauration gen-2 ACTIVE (rev 898), VÉRIFIÉE (revokedAt absent), Publish #4
  → READY/200 (restauration comportementale confirmée) ; session QA supprimée

Les 4 réserves de l'expert désormais levées (pin obligatoire + persisté
release/rollback + validation exhaustive catalogue + négatif live EXÉCUTÉ).
Artefacts bruts horodatés + hashes : docs/deploy-evidence/2026-07-23-ctr-runtime-nix-v4/.
Contrat v5, PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jul 31, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Jul 31, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 3, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 3, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 3, 2026
…tate (#1+#2)

Root cause of the flaky "prompt → app in Preview" loop: the runtime reseed
(importing persisted project files into the pod) is CLIENT-driven only
(ProjectWorkspaceProvider). A pod reached OUTSIDE a live IDE session is never
seeded — the remote adapter re-provisions a GC'd/reaped pod on the first agent
502 to serve a background terminal/file/preview op, and the new pod comes up
with an EMPTY /workspace. The op then runs against nothing: `npm run dev` →
ENOENT package.json, `sh: vite: not found`, blank Preview. Files "saved by the
agent" are not the files that run.

Reconcile it server-side at the source: whenever POST /workspaces (start) or
/restart brings a pod to RUNNING, list its root via the agent and, if it is
empty of project files, seed it from the authoritative persisted ide-state
(same source /export/zip derives via ensureProjectStorageFromIdeState).

- Content-driven, not flag-driven: fires for cold start, re-provision-after-GC,
  and a wiped PVC alike; no-ops on a warm pod that already carries its files
  (the common reattach path — one cheap /files/tree call).
- Best-effort: an unreachable agent or a mid-seed write blip never fails the
  start; the client reseed and the next runtime op still reconcile.
- Emits workspace_runtime_reseed_total so the fix is observable in prod.

Extracted the pure emptiness decision into runtime-reseed.ts with 7 unit tests
(empty tree, dotfile-only, mixed, malformed/non-array responses).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 3, 2026
+#2)

Live proof surfaced a sharper failure than an empty pod: after generation the
runtime carried src/, index.html and the configs but NOT package.json, while the
persisted ide-state had all 7 files — so `npm run dev` → ENOENT package.json,
`sh: vite: not found`, blank Preview. The first cut only reseeded a fully-EMPTY
pod, so it skipped this warm-but-diverged pod as "already populated".

Broaden the reconciliation from "seed if empty" to "ADD every persisted file
absent from the runtime tree":
- flattenRuntimeTreeFilePaths walks the agent /files/tree recursively into a set
  of file paths; runtimeFilesMissingFromPersisted diffs persisted → runtime.
- Purely ADDITIVE: an existing runtime file (possibly a newer edit) is never
  overwritten and a runtime-only file is left untouched — a stale persisted
  snapshot can only restore a missing file, never clobber live state.
- Still fires on start/restart→RUNNING, still best-effort, still no-ops on a
  fully-synced warm pod (one /files/tree call, zero writes). Metric renamed
  reason=restored-missing-from-persisted.

runtime-reseed.spec.ts rewritten: 9 tests incl. the exact package.json-only
divergence, empty pod, fully-synced no-op, additive/no-overwrite, path
normalization.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 3, 2026
…#2)

Live root-cause of blocker #1 (`sh: vite: not found` while npm install reports
success): it is a CONTENT divergence, not a missing file. After generation the
runtime's /workspace/package.json was a 200-byte stub — name + scripts only, with
NO dependencies/devDependencies — while the persisted ide-state carried the full
manifest (react, vite, @vitejs/plugin-react, …). `npm install` therefore saw zero
deps ("up to date, audited 1 package"), created no node_modules, and vite was
never present → `npm run dev` → sh: vite: not found → blank Preview. The
additive-only reconcile skipped it because the file EXISTS.

Broaden the reconcile to bring the runtime back in line with persisted: WRITE
every persisted file that is either absent OR present with different bytes.
- persistedFileContentMatches byte-compares persisted vs the agent /files/read
  reply, normalising utf8/base64 encodings — so a matching file is skipped (warm
  no-op) and only genuine divergence is rewritten.
- Scope stays exactly the persisted (editor-owned) file set: runtime-only files
  (build output, npm lockfile, node_modules) are never touched.
- Metric reason=reconciled-from-persisted with {missing, diverged} counts.

runtime-reseed.spec.ts: 13 tests incl. the exact stripped-package.json vs full
persisted divergence, cross-encoding compare, binary diff, warm no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 4, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 4, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 4, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 4, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 4, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 4, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 4, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 4, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 5, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 5, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 5, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 5, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 5, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 5, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 5, 2026
Answers the four reserves that refused PR #47, with REAL before/after E2E proof
on actual GCS + Kubernetes (not the memory adapters that were rejected).

1. Write barrier (#1): eraseSubjectStorage now calls WriteBarrierPort.freeze
   BEFORE any delete; a freeze failure aborts the erasure (nothing deleted, not
   verified) so nothing is recreated between erase/verify and the tombstone.
   Prod path: workspace-manager POST /workspaces/:id/freeze (revoke agent token
   + stop pod).

2. Real disappearance (#2): verification re-checks the LIVE backend — GCS list,
   and a new GET /workspaces/:id/pvc-exists that does a real  — never
   the workspace row's DELETED status (a partial k8s delete can leave a PVC).

3. By data subject (#3): the inventory is per-subject — the subject's sole-org
   buckets AND their per-user workspace in EVERY project they touched (sole-org
   + collaborator, via ProjectCollaborator), not just one main workspaceId.

4. Real proof (#4): two replayable, hashed E2Es under WIF-proof guardrails
   (dedicated test resources, no persistent keys, ~$0, full teardown):
   - GCS: GcsObjectStorage → eraseSubjectStorage against a throwaway bucket in
     the test project ecode-proof-b906ss; 3 objects → bucket+objects gone.
   - Kubernetes: a real Bound PVC on a throwaway local kind creates and manages local Kubernetes clusters using Docker container 'nodes'

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      Exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     Prints the kind CLI version

Flags:
  -h, --help              help for kind
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output
      --version           version for kind

Use "kind [command] --help" for more information about a command. cluster → deleted →
     verified gone via live  (kind, not GKE, so $0 — no cost sign-off
     needed per the guardrail).
   Artifacts + SHA256 in docs/deploy-evidence/2026-07-23-physical-purge-e2e/.
   Removes the old in-memory proof the expert rejected.

Module + route + real-Postgres suites green (33 tests). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openaxcloud pushed a commit that referenced this pull request Aug 5, 2026
… fail-closed + E2E negatives

1. k8s barrier fails on ANY delete failure: freezeWorkspace attempts every
   revoke but THROWS if any rejected (never claims the barrier / marks the row
   stopped while a write path may be live). Negative: manager.spec reserve #1.
2. Real GCS backend required: eraseSubjectStorage refuses (unverified, never
   deletes) when buckets exist but no active backend — a NoopObjectStorage can
   never certify absence. Negatives: module spec + REAL GCS E2E (inert backend
   refused, bucket survived).
3. Block ALL object-storage writes during purge: the barrier marks projects
   purge-frozen; upload-url/ensure-bucket/move return 403 OBJECT_STORAGE_PURGE_
   FROZEN, so nothing is recreated after the zero-check.
4. Inventory by REAL authorization: workspaces for EVERY project in ANY org the
   subject is a member of (shared orgs too, without a ProjectCollaborator row),
   plus explicit collaborations.
5. Only an authenticated NotFound = absence: pvcExists no longer swallows k8s.get
   errors — the client returns undefined only on a real NotFound and re-throws
   network/RBAC errors, so a read error fails closed. Negatives: manager.spec
   reserve #5 + kind E2E (surviving PVC reported present).
6. E2E negatives: real GCS + kind E2Es each carry a negative; the #1/#5 error
   negatives are proven deterministically in manager.spec (a k8s error cannot be
   reliably injected through kubectl/kind).

api + workspace-manager typecheck clean; 34 api purge tests + 48 manager tests
green; both real E2Es pass with negatives (artifacts hashed in
docs/deploy-evidence/2026-07-23-physical-purge-e2e/). PROVEN_REVIEW_PENDING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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