The realignment runbook — the locked plan (2026-05-25) matched against the verified reality (adversarial gap analysis 2026-06-13), the divergences, and the mission list to close them. Flow: dev → staging → main. The v1 runbook is left untouched.
This is the spine exactly as it was locked on 2026-05-25 (the imperative-crafting-wand strategic plan, 12 pillars / D1–D55). The flow was always simple: dev → staging (TEST) → main (PROD) — every change goes to staging, passes the whole battery again, then one human promote moves it to prod. No automated promotion. All 12 pillars ship before the first paying customer goes live.
The plan's spine is a strict 3-tier DEV / TEST / PROD topology with absolute prod isolation, and a one-way dev → preview-on-TEST → main → PROD promotion flow. Verbatim intent from the plan:
THE 3 TIERS (lines 304-327):
- DEV (laptops): local pnpm/npm + Vercel localhost + Neon `dev` branch + Vercel `development` env + live PlaySmart logs via read-only log splitter.
- TEST (cloned prod): NEW separate Hetzner project `plan-b-test`; servers TEST-siemsys-syslog + TEST-MDR-01 (3 containers); DNS test-siem.plan-b.co.il (Vercel preview alias) + test-siem-api.plan-b.co.il (TEST nginx, grey-cloud); Vercel `preview` env; Neon `dev` branch; Storage Box scoped /test/ (new BX11).
- PROD (untouchable): Hetzner PROD project; PROD-siemsys-syslog + PROD-MDR-01 (3 containers); siem-api.plan-b.co.il + siem.plan-b.co.il; Vercel `production` env; Neon `main` branch; Storage Box /prod/.
THE FLOW (lines 317-337): 'PR opens → Preview deploys to TEST → all Pillar 4-5 gates run against TEST data. Mike approves merge to main → PROD deploy → post-deploy verification runs → Auto-rollback on SLO breach.' Two boundaries: DEV↔TEST is ephemeral/frequent (every PR lands here; TEST is where dev 'shows itself' before being trusted); TEST→PROD is rare, human-approved (Mike merges to main), one-way only, NO automated promotion.
THREE ABSOLUTE RULES (lines 333-337): (1) No prod URL/DSN/key reachable from any non-prod code path (scripts/audit-prod-isolation.mjs enforces, gate #13 in check-all). (2) PROD has zero inbound dependencies on TEST or DEV (log splitter not log-forward; clones not mirrors). (3) Emergency override is Mike-only and audit-logged post-incident within 24h.
DATA-FLOW RULES (lines 322-327): PROD→TEST only via deterministic anonymizer (weekly staging refresh); TEST→PROD never (no code path, no script, no admin action); PlaySmart UDP 514 → log splitter → fans out to BOTH PROD-syslog AND TEST-syslog (its own cheap cx22; PROD has no outbound dependency on TEST). Per-PR ephemeral envs (Pillar 3 folded into Phase 1): Neon branch pr-{N} from dev + Vercel preview + PR-scoped OpenSearch index pr-{N}-test + auto-teardown on PR close.
A change reaches prod ONLY by passing the full staged-gate stack and then Mike merging the PR to `main`; there is NO automated promotion. The 5-layer defense-in-depth gate model (lines 619-640): Layer 1 local pre-push (Husky), Layer 2 CI on push (GH Actions: install/lint/typecheck/check-all/test/build), Layer 3 pre-merge branch protection (Layer 2 + Aikido + CodeRabbit no-outstanding-request-changes + on main: 1 approving review from Mike), Layer 4 pre-deploy rehearsal = Pillar 5 Stage 4 against TEST (Playwright/visual/a11y/perf/DAST/mutation/contract), Layer 5 post-deploy verification = Pillar 7 (synthetic + SLO burn-rate + auto-rollback). Merge to main is gated: branch protection requires all checks green AND Mike's approving review; no-bypass-for-admins is OFF so even Mike uses the PR flow (emergencies covered by the emergency-override runbook). On merge to main, Vercel deploys to PROD and post-deploy verification runs. Progressive delivery (Pillar 7): every meaningful PR ships behind a GrowthBook flag; canary ramps 1%→10%→50%→100% over 24h (4h/8h/12h gates); auto-rollback on ANY of {error rate 2x baseline, p99 2x baseline, synthetic fail 3x}; instant code rollback via Vercel alias switch (<30s) + flag flip. D30: ramp stages are manually approved by Mike until the Release Agent (Pillar 9 Tier 3) ships, automated after. The 'trust the gates' principle (D26, lines 1620-1623): post-launch Renovate auto-merges ANY green PR — patch/minor/major alike — because the CI/CD's entire job is to guarantee anything merged is safe; majors needing real migration fail gates naturally and stay open. System goes live to first paying customer only when ALL 12 pillars ship — NO phased go-live (line 2161).
The plan is portal-centric (Next.js → Vercel) for the dev→test→main→PROD spine; the MDR engine + edr-puller (the backend Docker containers on PROD-MDR-01 / TEST-MDR-01) are governed differently and the plan does NOT define a full git-promotion pipeline for them. Key locked intents:
1) Engine/puller are NOT modified from pbsiem PRs. CodeRabbit path_instructions (line 717): 'mdr-engine/**, edr-puller/** → contract-drift only; no code edits suggested (we don't modify these from pbsiem PRs).' The crypto-contract gate (lines 731-736) treats src/lib/mdr-crypto.ts (portal), mdr-engine/src/action-executor.ts decrypt, and edr-puller/src/decrypt.ts as 'read-only imports — we don't modify these files'; the gate only asserts the three decryptors stay contract-compatible against a golden v2 GCM envelope.
2) How the engine reaches prod = signed-container-pull, not Vercel alias. Pillar 6 (supply chain) sub-deliverable 5 (lines 1597, 1609): 'Sigstore-signed images for docker containers (mdr-engine, edr-puller) pushed to GHCR. GHCR push step in CI signs images via cosign sign (keyless OIDC). Engine + puller deployment scripts verify signatures before pulling.' Verification (line 1660): cosign verify ghcr.io/plan-b-systems/mdr-engine:<sha> returns valid keyless signature. So the engine reaches PROD-MDR-01 by deployment scripts pulling cosign-verified, SLSA-L3-attested images from GHCR.
3) The engine is woven into the cross-cutting pillars even though it isn't on the Vercel promote spine: OTel instrumentation in MDR engine + edr-puller with W3C TraceContext propagation portal→engine→puller (Pillar 8, lines 1212-1219); GrowthBook SDK in portal + engine + puller (Pillar 7 B, lines 1425-1431); shared crypto/rate-limit/audit-log cross-component contract gates (Pillar 4 H); cross-product attestation verification at deploy (Pillar 6 #9).
NET: the plan locks the engine's prod path as 'signed image in GHCR → verified pull onto the PROD Hetzner box,' kept honest by contract-drift gates and signature/provenance verification — but it does NOT specify a Vercel-style canary/alias-switch promotion or a TEST→PROD git-merge gate for the engine itself the way it does for the portal. This is the implicit gap: the promote-to-prod spine is portal-shaped; the engine relies on container-signing discipline rather than the dev→test→main button-press flow.
Each pillar’s locked intent and the decisions that shaped it. The current state of every one is in the Current state tab; what’s missing is in Gaps.
Every line here was checked against the actual repos on 2026-06-13 by a 16-agent adversarial analysis — not from memory, not from a prior session’s claims. This is the reality column for all 14 areas.
TEST stack is REAL and LIVE: test-siem-api.plan-b.co.il resolves to 178.105.214.148 and serves a valid Let's Encrypt cert (openssl Verify return code: 0). pbsiem/docs/01-infrastructure.md:80-97 documents the separate Hetzner CI/CD project: TEST-siemsys-syslog (132962657 / 178.105.214.148), TEST-MDR-01 (132962660 / 178.105.148.156, 3 containers engine+postgres+puller), splitter-syslog (133160347 / 178.105.116.35, rsyslog UDP-514 fan-out to PROD 88.198.217.216 AND TEST), network Siem-internal-test (12257988, 10.1.0.0/16), BX11 (584099, u601754.your-storagebox.de). Per-PR ephemeral WIRED: .github/workflows/ephemeral-env.yml calls cicd-system ephemeral-env.yml@main (Neon branch from parent "production", Vercel preview override) AND pr-backend-stack.yml@main with teardown on PR close (lines 19-56). Full-system TEST deploy WIRED (contradicts runbook's open gap): .github/workflows/stage4.yml header "THE RELEASE-CERTAINTY CORE (S5, D-4=Option C)" rebuilds mdr-engine+edr-puller at PR SHA via docker-build, deploys BOTH to the shared TEST stack via siem-deploy.yml@main with required-env-marker:TEST + health/RestartCount gate (lines 85-125), migrates MDR-PG, syncs receiver, then runs e2e/dast/perf/visual/link-crawl vs the real preview. Prod-isolation gate WIRED: scripts/check-all.mjs:52 invokes check-prod-isolation.mjs. ENGINE PROD PROMOTE: NONE. prod-deploy.yml + prod-promote.yml are VERCEL-PORTAL-ONLY (prod-promote.yml:48-64 calls vercel-promote.yml to move alias siemsys.plan-b.systems; prod-deploy.yml has zero engine/docker/ssh/hetzner steps). siem-deploy.yml is called only by stage4.yml + rollback-drill.yml (both TEST) — never for prod; its own header advertises a "deploy to PROD host (env-gated)" mode that no caller uses.
VERIFIED in pbsiem (plan-b-systems/pbsiem, local clone). (1) Rotation cron SHIPPED + WIRED: .github/workflows/rotate-secrets.yml:42 cron '19 20 * * 5' + workflow_dispatch; scripts/rotate-secrets.sh rotates exactly TWO secrets — OPENSEARCH_PROXY_KEY (line 122/149-150) and mdr_admin PG password (line 116-117, ALTER USER at 206-210). Host discovery via GET on src/app/api/cron/rotation-targets/route.ts (route.ts exists, 1884 bytes) using CRON_SECRET bearer. NO MDR_ENCRYPTION_KEY, bearer-token, PAT, or GrowthBook rotation present. (2) "No human sees prod credentials" ALIGNED for the 2 rotated secrets: rotate-secrets.sh:355-359 logs sha256 fingerprints (first 16 chars) only; plaintext values file shredded on every exit path (trap EXIT INT TERM, line 135; confirmed 361-363). (3) getSecret() proxy MISSING: no src/lib/secrets/get-secret.ts (dir absent); the only getSecret matches are unrelated LOCAL helpers — src/lib/trusted-browser.ts:21 (HMAC signing key) and src/lib/twilio-callback-token.ts:32 — NOT the central audited proxy. (4) SECRET_ACCESS audit MISSING: grep "SECRET_ACCESS" across pbsiem returns ZERO hits; AuditLog model exists (prisma/schema.prisma:586) but action is a free-text string with no secret-access usage. (5) OIDC correctly DEFERRED: long-lived VERCEL_TOKEN still referenced by 8 workflows (ephemeral-env, post-deploy-verify, prod-deploy, prod-promote, prod-verify, rotate-secrets, stage4, visual-baselines). (6) Per-env scoping PARTIAL: GH environment: keyword only in prod-deploy.yml; rotation script keeps preview/TEST creds separate from PROD (lines ~154). (7) docs/secrets.md MISSING.
WIRED AND RUNNING GREEN today (verified 2026-06-14). cicd-system ships reusable `workflow_call` workflows `.github/workflows/ephemeral-env.yml` (Neon branch create/reuse with read_write endpoint, hard-fail on wrong parent, EPHEMERAL_PAUSED gate, connection-uri retry, Vercel preview vars, seed, PR comment, full teardown on close) and `pr-backend-stack.yml` (per-PR engine+puller+postgres docker-compose on TEST pool host 178.105.148.156, deterministic HMAC PG password, pool eviction/MAX_STACKS, image GC). pbsiem `.github/workflows/ephemeral-env.yml:19` calls the cicd reusable @main with neon-project-id autumn-thunder-97996128, neon-parent-branch=production, seed-script scripts/qa-seed.ts, pr-stack-host. pbsiem `stage4.yml` deploys engine/puller/receiver to TEST + stands up the PR's own backend stack + runs e2e/dast/perf/visual/links against the PR's real Vercel preview. Live: `gh run list` shows ephemeral-env success (14-44s) and stage4 success (10-13m) on 2026-06-14; PR #78 stage4 jobs ALL green incl. deploy-engine/deploy-puller/pr-stack/pr-services/e2e/links. Crons exist: `src/app/api/cron/ephemeral-env-gc/route.ts` (Neon-only, 03:00) and `ephemeral-env-cost/route.ts` (Neon branch count, 04:00, dual-auth) in pbsiem vercel.json:192/196. Admin UI at `src/app/admin/ephemeral-envs/page.tsx` (not the plan's (admin)/admin/infra path).
VERIFIED on pbsiem origin/main + gh + cicd-system. Layer 1 EXISTS: .husky/pre-commit=`npx lint-staged`, .husky/pre-push=`npm run prepush`, .nvmrc=22.16.0 (plan said 22.11.0), husky^9.1.7 + lint-staged^15.4.3, lint-staged config matches, engines>=22.11.0. BUT `prepush`=`npm run check && npm run typecheck && npm run lint && npm run test:unit` — uses `npm run lint` (NOT lint:strict) + `test:unit` (NOT full test); diverges from plan AND from README L28 which claims `--max-warnings 0` + `vitest run`. Layer 2 BUILT-DIFFERENT: pbsiem/.github/workflows/ci.yml calls reusable cicd-system/.github/workflows/ci.yml@main as ONE combined job (single required check `ci / CI`, sequential steps), NOT 6 separate checks; pbsiem passes `lint-command: "npm run lint"` overriding the reusable default `lint:strict` — lint-warnings-as-errors NOT enforced in CI. check-all.mjs runs 13 lints + auto-discovers scripts/check-contracts/*.mjs (crypto-envelope.mjs present) — D17 framework BUILT+WIRED+ENFORCED (aligned). Aikido MISSING/REPLACED: cicd-system/.github/workflows/security-scan.yml L1+L3 = "Security Scan (Semgrep + Trivy) … Replaces Aikido (D54 superseded)"; runs Semgrep+Trivy only, NO Aikido step; pbsiem security-scan.yml enforce:true block CRITICAL,HIGH w/ allowlist. CodeRabbit: app IS installed (coderabbitai reviewer; PR#78 check `CodeRabbit pass / Review skipped`) but ADVISORY — .coderabbit.yaml does NOT exist on origin/main (removed commit fb8a2fb "remove CodeRabbit — can't downgrade to free tier"), request_changes_workflow defaults FALSE, NOT a required check. Branch protection (gh api): dev strict=False, 10 checks {ci/CI, impact, dast, e2e, perf, verify, visual, Vercel, links, security-scan/scan}, 0 reviews, enforce_admins=False; main strict=True, ONLY {ci/CI}, req_approvals=0, enforce_admins=TRUE, force=off. Neither branch lists Lint/Typecheck/Test/Build/Aikido/CodeRabbit as separate checks; main has 0 approvals (plan wanted 1) + enforce_admins ON (plan wanted OFF). Docs: README Contributing 5-layer + --no-verify→docs/runbooks/prod-emergency-override.md; docs/runbooks/ci-gate-failures.md EXISTS; ARCHITECTURE.md L133 still says "6 parallel jobs" + "Aikido + CodeRabbit" pre-merge — STALE.
VERIFIED. cicd-system holds reusable workflows for the whole battery (.github/workflows/: playwright-e2e.yml, visual-regression.yml, perf-budget.yml [Lighthouse+k6], a11y-i18n.yml [axe-core + i18n parity], mutation-test.yml [Stryker], dast-zap.yml, link-crawl.yml). pbsiem stage4.yml (lines 208-269) wires ONLY 5 gates to its per-PR run: e2e, dast, perf, visual, links — plus a genuine full-system TEST deploy (build-engine/build-puller/migrate-test-db/deploy-engine/deploy-puller/deploy-receiver) AND a per-PR isolated backend stack (pr-stack/pr-db/pr-services with synthetic-seed.ts), which EXCEEDS the plan's "ephemeral env" scope. dev branch protection required contexts (gh api): ci/CI, impact, dast, e2e, perf, verify, visual, Vercel, links, security-scan — confirmed live and green on real PRs (stage4.yml runs 2026-06-13/14 = success). BUT: (1) e2e runs ONLY tests/e2e/dev-environment.spec.ts (stage4.yml:218) of 90 spec files — not the 86-spec suite; workflow comment admits "broaden... once the ephemeral DB seeding is wired deterministically." (2) visual covers 3 pages login/pricing/status, EN only, baselines tests/e2e/visual-regression.spec.ts-snapshots/*-chromium-linux.png — not 24 EN+HE. (3) perf = lighthouse-urls "/,/login", configs/lighthouse-budget.json static budget, k6-script-path empty (no k6, no rolling-from-main baseline). (4) i18n-parity.yml calls a11y-i18n.yml but ONLY the i18n job, gated on paths src/lib/i18n/** and NOT in branch protection (last run 2026-06-10 green). (5) axe-core accessibility job: NEVER called by pbsiem. (6) mutation-test.yml: NEVER called by pbsiem. (7) cross-tenant: tests/e2e/security/ does not exist; grep cross-tenant = 0 hits. (8) zod-to-openapi/oasdiff: 0 hits anywhere. (9) AICostLedger: not in prisma/. (10) migration safety: no check-migration-safety.mjs; only scripts/check-contracts/crypto-envelope.mjs exists (stage4 does honest prisma db push WITHOUT --accept-data-loss, a partial substitute). GAP A confirmed: main branch protection required check = ONLY "ci / CI" — the Stage-4 battery does NOT re-run on the dev→main promotion.
VERIFIED in code + live gh: (1) SBOM — sbom.yml reusable exists (cicd-system/.github/workflows/sbom.yml) and IS wired: pbsiem/.github/workflows/sbom.yml calls it @main on push to main; live runs succeed (most recent 2026-06-14T01:06 success). BUT it diverges from plan: uploads a 90-DAY-retention artifact (sbom.yml:68 retention-days: 90), NOT attached to any GH Release, NOT pushed to Storage Box forever — and it only SBOMs npm portal deps, never the container images. (2) SLSA L3 — slsa-provenance.yml reusable exists (cicd-system) and is well-formed (attest-build-provenance@v2, gh attestation verify), but has ZERO callers: 404 for the workflow in pbsiem, and an org code-search for slsa-provenance.yml@main returns nothing. Its release-attach step (slsa-provenance.yml:54 `if: github.event_name == 'release'`) can never fire — pbsiem has NO releases at all (gh release list empty). => SLSA = built-but-not-wired. (3) Sigstore image signing — docker-build.yml (cicd-system) builds+pushes mdr-engine/edr-puller to GHCR with NO cosign sign step (docker-build.yml:74-95); pbsiem/docker-build.yml calls it unchanged. siem-deploy.yml pulls/ssh-loads images with NO cosign verify (siem-deploy.yml:109-191). grep for "cosign" across cicd-system (non-node_modules) hits only docs + a findings route, never a workflow. => image signing & verify = MISSING. (4) SLSA Source Track L1 — ALIGNED: branch protection is enforced (per the plan's chosen L1 target satisfied by branch protection; signed commits correctly deferred). (5) Renovate / dep-audit cron — MISSING: no .renovaterc.json in pbsiem or cicd-system; no osv-scanner/npm-audit-cron workflow in either repo. (6) Supply-chain risk score — MISSING: no Grafana/dashboard pane; the only dashboard "provenance" string is the Vercel promotion-PR matchesMainTip check (promotions/page.tsx:201,466), unrelated to SLSA/cosign. (7) docs/supply-chain.md — MISSING (no such file). Note: security-scan.yml:3 says "Replaces Aikido (D54 superseded)" — Aikido is actually OUT in code (Semgrep+Trivy only), contradicting build.py:169's "Aikido Back IN" claim.
The canary/flag machinery is BUILT-BUT-NOT-WIRED, and several pieces are MISSING. (1) cicd-system/.github/workflows/canary-gate.yml exists with real logic: GrowthBook PUT /api/v1/features rollout-coverage ramp following the D28 default schedule (line 39), 5-min gate-window error-rate-vs-baseline checks, and on breach a GrowthBook flag-disable + workflow fail (lines 128-219). BUT it is a workflow_call reusable invoked by NOTHING real — grep shows the only reference is configs/example-callers/canary-caller.yml; zero pbsiem workflows reference canary/growthbook/feature-flag (grep of pbsiem/.github/workflows returned empty). (2) GrowthBook is NOT deployed: configs/growthbook-setup.md is a doc only (docker-compose snippet inside markdown), and all three planned hosts (gb.plan-b.co.il, growthbook.plan-b.co.il, gb-api...:3100) return connection failure (curl 000). No @growthbook/growthbook SDK in portal/engine/puller. (3) NO scripts/rollback-prod.mjs exists (find scripts/deploy returned only unrelated scripts). The Vercel-alias primitive instead lives in pbsiem prod-promote.yml -> cicd vercel-promote.yml, used as a HUMAN PROMOTE (alias repoint, no ramp); and a Vercel promote-previous rollback is inline in post-deploy-verify.yml (lines 130-183) but is SEALED OFF on production (auto-rollback default false; lines 29-33, 191 enforce verify+page-Mike, never auto-rollback on prod per S6). (4) NO /api/cron/canary-ramp, NO /api/cron/burn-rate-rollback. (5) NO /admin/canary or /admin/deploy/rollback UI in pbsiem (grep empty). (6) NO Migration Review Agent and NO expand-contract enforcement anywhere — cicd change-impact.yml only applies a "schema-changing" LABEL to prisma/schema + /migrations/ paths (lines 71-76), no prisma migrate diff, no ALTER blocking, no expand-contract suggestion; pbsiem change-impact.yml does not even reference prisma. (7) NO canary AuditLog event types (grep for CANARY_RAMP/CANARY_ROLLBACK/PROD_DEPLOY_ROLLBACK in pbsiem returned empty). The TEST-only rollback-drill.yml (container restore, PLANB_ENV=TEST assert) WAS exercised (S6: restore in ~1s/21s) — that is the only progressive-delivery-adjacent thing proven to work, and it is engine container restore, not canary or alias rollback.
VERIFIED. The TEST obs stack genuinely exists and runs: observability/docker-compose.yml (Prometheus v2.55.1, Alertmanager v0.27.0, Grafana 11.3.1, node-exporter, blackbox v0.25.0, otel-collector-contrib 0.114.0, Caddy) on the Hetzner Build-Runner; grafana.plan-b.co.il/api/health returns 200 {database:ok, version 11.3.1} — confirmed live by curl. slo/slo-definitions.json holds the 6 SLOs; observability/generate-slo-alerts.mjs renders observability/prometheus/rules/slo-alerts.yml (11 alerts). BUT: (1) Those alert rules query http_requests_total{slo=...} and http_request_duration_seconds_bucket{slo=...} — metrics that NOTHING emits. pbsiem/src/instrumentation.ts:8-15 only imports sentry.server/edge config; there is NO @vercel/otel, no OTLP exporter, no registerOTel, no W3C propagation in portal/engine/puller (grep of pbsiem src = 0 hits outside node_modules). The collector's traces pipeline exports to 'debug' (collector.yaml:36, discarded). So the burn-rate alerts can never fire on real traffic — they sit at 0 baseline. (2) The dashboard's SLOs are a DIFFERENT set: dashboard/src/lib/slo-compute.ts:65-93 computes 'CI success rate', 'CI p90 duration', 'Deployment success rate' from the dashboard's OWN build/deployment rows — NOT the 6 MDR SLOs. dashboard/src/app/api/slo/route.ts serves these. (3) Cost attribution: ZERO client_id or feature_flag labels anywhere in observability/ (grep = no matches). dashboard /api/metrics (route.ts) exports only cicd_* platform metrics; the sole cost metric is cicd_weekly_estimated_cost_eur from EphemeralEnvCost (ephemeral-env spend), not per-customer/per-feature. No cost-exporter code exists (Glob **/cost-exporter* = none). (4) Sentry: tracesSampleRate 0.05 prod (not 0.1) and Session Replay deliberately disabled (sentry.client.config.ts:11-16) — no client_id in beforeSend. (5) Better Uptime: not integrated; 'synthetic monitoring' = blackbox probes against only 2 URLs (prometheus.yml:51-53), not 6 at 1-min via Better Uptime. (6) Alertmanager routes to ALERT_WEBHOOK_URL = the CI/CD dashboard webhook (writes ALERTMANAGER_WEBHOOK audit rows), NOT pbsiem's SVC-HEALTH /admin/infra/service-health surface the plan names. (7) D38 PROD-obs-stack: not provisioned (TEST-only).
A real, coherent agent FRAMEWORK exists in cicd-system/agents/ but is invoked by NOTHING. Files: framework/types.ts (AgentTier enum 0-4, AgentSpec, TrustThreshold), framework/runtime.ts (TIER_PERMISSIONS map, createAgentRuntime with hand-rolled pre/post-tool-use hooks + tier+allowlist+cost gating, loadAgentSpec/listAgentSpecs), framework/telemetry.ts (recordInvocation→local JSON, checkEarnedTrust, TRUST_THRESHOLDS), framework/cost-guard.ts (MODEL_PRICING, DEFAULT_CAPS per-run 0.5/per-PR 5/per-repo-mo 50, checkCostBudget/recordCost). 10 specs in agents/specs/*.json (migration-review T0, pii-detection T0, cost-anomaly T0, ci-triage T1, doc-drift T1, visual-regression-triage T1, test-generation T1, dependency-update T1, oncall-investigation T2, release T3). WIRING: grep for createAgentRuntime/loadAgentSpec/runAgent/agents/framework hits ONLY docs/cicd-platform-build-plan.md, agents/README.md, and the framework's own runtime.ts/index.ts — zero callers in dashboard/, .github/workflows/ (none of the ~22 workflows reference it), scripts/, or pbsiem. No root package.json/tsconfig — the framework TS is not compiled into any build target; no *.test.* for the framework. D39 VIOLATED: no Claude Agent SDK dependency anywhere; allowedTools is a plain JSON string[] checked by hand (runtime.ts:201-228), not the SDK's API-layer allowedTools; hooks are hand-coded, not SDK PreToolUse/PostToolOutput. D41 earned-trust numbers match (telemetry.ts:30-34) BUT there is NO SHADOW/LIVE field anywhere and checkEarnedTrust only REPORTS eligibility — never auto-graduates (README:119 "Promotion is not automatic"); also overrideRate is computed as "human overrode a tier DENIAL" (telemetry.ts:96), NOT the plan's "human did something different than the agent suggested" — a different metric. D42 VIOLATED: telemetry is local gitignored JSON (agents/telemetry/*.json), not OTel/Prometheus/Grafana. MISSING entirely: <untrusted> channel (grep: only in docs), agent-to-agent graph cap / cascade cost (grep: only docs), golden-input suites (none), monthly post-mortem + agent-off-day (none), AgentTrustState + AICostLedger persistent models (absent from pbsiem schema — the only "trust" models are TrustedDevice/Persona-4 trust-period, unrelated), CSA framework alignment (terminology not present). The 4 existing agents are NOT retrofitted/tier-declared. The ONLY actually-WIRED agent-fleet-adjacent thing is dashboard/src/lib/promotion-verdict.ts: two-LLM advisory "verdict reviewers" (Anthropic claude-sonnet-4-6 + OpenAI gpt-4o) called from /api/promotions/verdict, /api/promotions, and /api/cron/promotion-notify, persisted to AuditLog (action=AI_VERDICT), surfaced in the Promotions pane / scoreboard — but it is a standalone raw-HTTP implementation: it does NOT import agents/framework, has no tier runtime, no cost guard, no allowedTools, and feeds the commit message into the prompt WITHOUT <untrusted> wrapping (promotion-verdict.ts:35-68). It is advisory/shadow only (never gates the button, Safety Rule 5).
PARTIAL. What EXISTS and is WIRED: (1) C5 change-impact auto-tagging is LIVE — reusable workflow cicd-system/.github/workflows/change-impact.yml applies all 5 plan labels (auth-touching/schema-changing/customer-facing/security-relevant/pii-touching, lines 62-103) incl. content-based PII diff scan (146-162); pbsiem/.github/workflows/change-impact.yml:17 calls it @main on pull_request to dev/main; gh shows successful runs 2026-06-14 (run 27485718386, 14s). (2) An AuditLog store EXISTS on the dashboard (cicd-system/dashboard/prisma/schema.prisma:265-280) and is actively WRITTEN — but only for dashboard CONTROL-PLANE actions: promotions/dispatch (controls/dispatch/route.ts:84, promotions/dispatch:72, merge-pr:86, promotion-notify:106), security finding status (security/findings/[id]/status:59, repair-queue:127), webhooks/autofix:58, webhooks/alertmanager:45. (3) MDR has its own AuditLog model (pbsiem/prisma/schema.prisma:586-598: admin_email/action/target/details/ip/created_at) + check-audit-coverage.mjs gate + check-pci-body-logging.mjs gate, both in check-all.mjs (lines 35,41). BROKEN/NOT-WIRED: (a) C1 pipeline-layer audit is a WRITE-TO-VOID — cicd-system/.github/workflows/audit-log.yml POSTs a custom payload (repo/workflow/run_id/changed_files/duration_seconds) with header X-GitHub-Event:workflow_run to /api/webhooks/github, but dashboard/src/app/api/webhooks/github/route.ts:55-64 requires payload.repository and returns HTTP 400 "Invalid payload" for that shape; the workflow swallows non-2xx as a ::warning:: (audit-log.yml:91) and still "succeeds." The Build table is populated by the org GitHub App's NATIVE workflow_run events, NOT by audit-log.yml — its data is never persisted to any audit store. MISSING ENTIRELY (verified absent in both repos via find): compliance/ directory; C2 soc2-control-map.yaml; C3 pci-control-map.yaml; D47 pci-tranzila-scope-letter.pdf; C4 docs/data-flows.md; C6 auditd configs; C7 compliance/drills/ signed reports; C8 /api/customer/audit-export (no audit-export endpoint anywhere; only admin/audit + admin clients export-request exist); C9 retention enforcement (no 7yr/5yr crons, no append-only/immutability guard on either AuditLog model); C10 docs/compliance.md. No SOC 2 / PCI / GDPR mapping artifacts exist at all.
The one in-scope DR item is BUILT-BUT-NOT-WIRED, partially fake, and architecturally divergent from D1. (1) .github/workflows/backup-restore-verify.yml exists on plan-b-systems/cicd-system main but is `on: workflow_call` ONLY (lines 3-4). Commit 2dd43a8 ("fix: remove schedule trigger from backup-restore-verify workflow", 2026-05-28) DELETED the `schedule: cron '0 3 * * *'` (was at the top of the file) because it kept failing on cicd-system itself (no NEON_API_KEY) — with the stated intent that a consumer repo would call it. NO consumer repo ever did: grep across pbsiem and cicd-system finds ZERO callers (only ARCHITECTURE-CI.md prose references). The last 8 GH runs are all 0s `failure` (the pre-removal self-repo failures). (2) The workflow restores a Neon branch into a Neon EPHEMERAL branch (backup-restore-verify.yml:82-126), NOT "yesterday's PROD backup INTO TEST" as D1 demands — it never touches a real backup artifact or the TEST stack. (3) The "Verify row counts within tolerance" step (lines 169-193) is a NO-OP: it unconditionally writes `deviation_ok=true` whether or not data exists — the advertised tolerance check does nothing. (4) It covers only the Neon portal DB; the actual MDR product data (PostgreSQL on PROD-MDR-01, infra/prod-mdr-01/README.md) is not in this workflow. (5) docs/cicd-platform-build-plan.md:216 specifies the cron-route implementation that was never built — src/app/api/cron/backup-restore-verify/route.ts does not exist. (6) The host-side MDR PG daily backup (02:30 -> Storage Box) claimed in build.py:872 is not verifiable in any committed repo artifact — infra/prod-mdr-01/backups/ holds only one manual April pre-stage1 pgdump; no cron unit/script is committed. (7) Closest live "drill" is pbsiem .github/workflows/rollback-drill.yml (active, but workflow_dispatch only) — a Pillar 7 container-restore rehearsal on TEST, NOT the DR D4 quarterly drill (no RTO/RPO measurement, no signed report, not scheduled). (8) No DR docs exist (docs/dr.md, docs/runbooks/dr-drill.md, docs/runbooks/failover-decision.md all absent) — but those are out-of-scope per D49.
M10 partial-but-divergent: the reusable-workflow store EXISTS as plan-b-systems/cicd-system (27 reusable workflows under .github/workflows/) and is genuinely consumed — pbsiem has 30 `uses: plan-b-systems/cicd-system/.github/workflows/*.yml@main` refs (ci.yml:18, ephemeral-env.yml:19/48, post-deploy-verify.yml:26/53, prod-promote.yml:48/72, ...) and vaughnblades consumes 3 (ci/sbom/security-scan, all @main). BUT: (1) the repo is PRIVATE (gh repo list: `plan-b-systems/cicd-system ... private`), violating D51 PUBLIC; (2) NO plan-b-systems/ci-templates repo exists (gh: "Could not resolve to a Repository"); (3) NO SemVer tags / @v1.0.0 — consumers pin @main (30/30 refs in pbsiem, 0 SHA-pins, 0 @v pins), directly violating the plan's "never @main" rule even though README.md:18/24 DOCUMENTS "@<sha>... Pin by SHA, not branch. Renovate auto-updates" (discipline written, not practiced). vaughnblades (39 merged PRs, a live real product) IS the de-facto conformance test, onboarded from cicd-system. M6 anonymizer: only scripts/anonymize-seed.mjs exists — a SQL-stream REGEX scrubber over pg_dump text (emails/phones/IPs/cards), NOT the plan's schema-aware `scrub(row, schema)` library; no weekly-TEST-refresh wiring; orphaned vs the separate S2b/S2c scripts/dev-scrub logic. M1 services.yaml: ABSENT in both repos (Glob: no files) even though the agents framework + the very specs it must feed exist (agents/specs/cost-anomaly.json, oncall-investigation.json, doc-drift.json) — those agents have no catalog to read (compounding the 'agent framework built but wired to nothing' divergence). M2 Terraform: ZERO .tf files anywhere (Glob: no files); infra hand-managed. M3 runbooks-as-code: docs/runbooks/*.md exist in both repos but are free-form (auto-fix.md, link-crawl.md, ...) with NO required-section schema and NO CI validation. M4 chaos: NO chaos scripts; the only 'chaos' hits are in pbsiem's stale ARCHITECTURE-CI.md (Litmus/Stage-7 staging — a different unbuilt design) and the plan-mirror doc. M5/M7/M8/M9: not built — no status-page-to-SLO binding, no X-Deprecated/API-versioning policy, no coherent customer export surface, no PII-scan gate over fixtures/visual baselines (security-scan.yml:85 runs Trivy `vuln,secret,misconfig` which incidentally catches secrets but is NOT the planned fixture/screenshot PII gate).
Topology: pbsiem docs + runbook confirm 3 TEST servers + plan-b-test project (not re-verified live this session). Flow is built DIFFERENT from the plan: (1) per-PR ephemeral = portal Vercel preview + Neon branch ONLY (ephemeral-env.yml); (2) stage4.yml deploys engine+puller to the SHARED TEST stack via siem-deploy.yml with required-env-marker:TEST + TEST_MDR_DEPLOY_HOST (stage4.yml:89-127) — shared, serialized cross-PR, not per-PR isolated; (3) the plan's human merge-to-main gate is REPLACED by promotion-autopilot.yml (PROMOTION_AUTOPILOT var = on, verified live) which auto-opens dev→main promotion PRs + "fold" PRs and auto-merges them once certify is green — removing "the human merge step in the middle" (promotion-autopilot.yml:8-9); (4) PROD reached by prod-deploy.yml (push to main → vercel-gated-deploy, staging artifact, NO alias move) then a SEPARATE human Promote click (prod-promote.yml dispatched by dashboard /api/promotions/dispatch/route.ts) that moves the Vercel alias only. Live: prod-deploy run 27484292775 (PR #77) + Promote 27484423128 both succeeded 2026-06-14; fold PR #76 active. ENGINE PROD PATH MISSING: siem-deploy.yml is called ONLY by stage4.yml (TEST) and rollback-drill.yml — there is NO caller that deploys the engine/puller to PROD-MDR-01. docker-build.yml pushes to GHCR but the cicd-system docker-build reusable has NO cosign sign step, NO SLSA attestation; zero slsa-provenance/attest-build-provenance callers in pbsiem; siem-deploy.yml does NO cosign-verify before pulling. So the plan's signed-image→verified-pull engine prod spine does not exist; the engine only ever reaches TEST. Staging-data refresh MISSING: no anonymize-seed.mjs / no scrub(row,schema) library / no verify-dev-scrub / no weekly-refresh cron in pbsiem (dev DB drifts → gate flake, the P0 the owner hit). Prod-isolation gate ALIGNED-renamed: scripts/check-prod-isolation.mjs (config-driven via audit-prod-isolation.config.yaml) IS wired into scripts/check-all.mjs:52.
Verified in pbsiem/.github/workflows: prod-deploy.yml implements verify-BEFORE-promote (header L1-28: "production alias is STRUCTURALLY unreachable by an unverified artifact"; calls vercel-gated-deploy.yml@main with promote-after-verify:true, auto-promote:false, prod-alias siemsys.plan-b.systems) and parks a staging artifact ("READY TO PROMOTE", proven run 27328491331 per cicd vercel-gated-deploy.yml:8). prod-promote.yml is a separate workflow_dispatch human gate that re-verifies provenance, repoints the alias, then crawls the live alias (prod-promote.yml:6-72) — Gap B is CLOSED in code. stage4.yml (L41-126) DOES build mdr-engine + edr-puller at the PR SHA, applies the TEST DB schema, and deploys BOTH to TEST-MDR-01 via siem-deploy.yml@main with PLANB_ENV/health-gated rollback — full-system per-PR TEST deploy is WIRED (contradicting the gaps-tab "open" claim). certify.yml exists on PRs to main, mechanically gates on protected_merge + introducing-PR gates, and runs an AI CERTIFY/REFUSE only when ANTHROPIC_API_KEY exists (certify.yml:145-161) — dormant by design. promotion-autopilot.yml opens a dev→main PR and ARMS GitHub auto-merge once certify=CERTIFY + checks green (L92-137) — an automated promotion path the plan forbids. MISSING on critical path: (a) NO engine→PROD deploy/promote workflow — docker-build.yml builds engine/puller images on push to main but the only engine DEPLOY targets are TEST (stage4, rollback-drill); PROD-MDR-01 (178.104.172.142) appears only in rotate-secrets known-hosts, never as a deploy target. (b) NO scripts/dev-scrub/ dir and NO scheduled TEST-data-refresh / anti-drift cron in pbsiem (only rotate-secrets cron exists) — staging data goes stale, which is the documented gate-flake root cause.
Reality is ahead of the runbook in the TEST/ephemeral substrate but behind the LOCKED PLAN on the spine. Per-PR ephemeral env, full-system TEST deploy (mdr-engine + edr-puller + receiver + isolated per-PR backend stack), and a 5-gate battery (e2e/dast/perf/visual/links) are genuinely WIRED and green on real pbsiem PRs to dev, exceeding the runbook's stale 'portal + DB branch only' claim. BUT the locked TEST->PROD spine is broken: prod-deploy.yml and prod-promote.yml move ONLY the Vercel portal alias (verified: zero engine/docker/ssh/hetzner steps; siem-deploy.yml's env-gated PROD mode has no caller), so the engine reaches PROD only via out-of-band manual deploy with no verified-staging-to-prod promotion. That is the P0 that turned a 40-minute change into 11 hours. Compounding it: the shared TEST stack's MDR-PG/OpenSearch data is never refreshed (verified: no weekly anonymized dump), so gates flake on empty/stale data; staging is unlabeled and the only human gate is a portal-alias click. Pillar 4 quietly relaxed (lint:strict not enforced in CI, Aikido absent despite a runbook claiming 'back IN', CodeRabbit advisory, main gated by only ci/CI with 0 approvals yet enforce_admins ON). Pillar 5's battery is real but thin (1 smoke spec of ~86, 3 EN visual baselines of 24, no k6/a11y/mutation/cross-tenant/oasdiff/migration-safety). Pillar 2 and cost models (EphemeralEnvCost, AICostLedger) are largely unbuilt. Net: the engine works for TEST; the plan's release-certainty promise to PROD does not yet exist mechanically.
129 findings across 14 areas, each typed and evidenced. Missing = never built. Built · not wired = exists in cicd-system but pbsiem never calls it. Built different = exists but diverges from the plan. Aligned = matches the plan (collapsed). The Tasks tab turns these into an ordered mission list.
Close the two P0s — the engine promote path and the staging-data refresh — and a Java/engine upgrade goes through staging and out to prod as one promote, not an 11-hour hand-deploy. The other 21 are real, but they are hardening, not blockers. Wire what exists, align what drifted, build what’s missing.
Fastest wins — the workflow already exists in cicd-system; pbsiem just never calls it.
Code, runbook, and plan disagree. Fix the behavior, then make all three agree.
Net-new. The two P0s live here.
זהו עמוד השדרה בדיוק כפי שננעל ב-2026-05-25 (התוכנית האסטרטגית imperative-crafting-wand, 12 פילרים / D1–D55). הזרימה תמיד הייתה פשוטה: dev → staging (TEST) → main (PROD) — כל שינוי עובר ל-staging, עובר שוב את כל סוללת הבדיקות, ואז promote ידני יחיד של אדם מעביר אותו ל-prod. אין promotion אוטומטי. כל 12 הפילרים חייבים להישלח לפני שהלקוח המשלם הראשון עולה לאוויר.
עמוד השדרה של התוכנית הוא טופולוגיית 3 שכבות קשיחה — DEV / TEST / PROD — עם בידוד מוחלט של ה-PROD, וזרימת promotion חד-כיוונית של dev → preview-on-TEST → main → PROD. הכוונה המדויקת (verbatim) מתוך התוכנית:
שלוש השכבות (lines 304-327):
- DEV (לפטופים): pnpm/npm מקומי + Vercel localhost + ענף Neon `dev` + סביבת Vercel `development` + לוגים חיים של PlaySmart דרך log splitter במצב read-only.
- TEST (clone של prod): פרויקט Hetzner נפרד וחדש `plan-b-test`; שרתים TEST-siemsys-syslog + TEST-MDR-01 (3 containers); DNS test-siem.plan-b.co.il (alias של Vercel preview) + test-siem-api.plan-b.co.il (nginx של TEST, grey-cloud); סביבת Vercel `preview`; ענף Neon `dev`; Storage Box מתוחם ל-/test/ (BX11 חדש).
- PROD (בלתי-נגיע): פרויקט Hetzner של PROD; PROD-siemsys-syslog + PROD-MDR-01 (3 containers); siem-api.plan-b.co.il + siem.plan-b.co.il; סביבת Vercel `production`; ענף Neon `main`; Storage Box /prod/.
הזרימה (lines 317-337): 'PR נפתח → Preview נפרס ל-TEST → כל ה-gates של Pillar 4-5 רצים מול נתוני TEST. Mike מאשר merge ל-main → deploy ל-PROD → אימות שלאחר ה-deploy רץ → Auto-rollback במקרה של חריגת SLO.' שני גבולות: DEV↔TEST הוא ארעי/תכוף (כל PR נוחת כאן; TEST הוא המקום שבו ה-dev 'מראה את עצמו' לפני שזוכה לאמון); TEST→PROD הוא נדיר, מאושר על ידי אדם (Mike עושה merge ל-main), חד-כיווני בלבד, ללא promotion אוטומטי.
שלושה כללים מוחלטים (lines 333-337): (1) שום URL/DSN/key של prod לא נגיש משום נתיב קוד שאינו-prod (scripts/audit-prod-isolation.mjs אוכף, gate #13 ב-check-all). (2) ל-PROD אין שום תלות נכנסת ב-TEST או ב-DEV (log splitter ולא log-forward; clones ולא mirrors). (3) emergency override שמור ל-Mike בלבד ונרשם ב-audit לאחר התקרית בתוך 24 שעות.
כללי זרימת נתונים (lines 322-327): PROD→TEST רק דרך anonymizer דטרמיניסטי (רענון staging שבועי); TEST→PROD לעולם לא (אין נתיב קוד, אין script, אין פעולת admin); PlaySmart UDP 514 → log splitter → מפצל לשני היעדים: PROD-syslog וגם TEST-syslog (cx22 זול משלו; ל-PROD אין תלות יוצאת ב-TEST). סביבות ארעיות per-PR (Pillar 3 מקופל לתוך Phase 1): ענף Neon pr-{N} מתוך dev + Vercel preview + אינדקס OpenSearch מתוחם ל-PR pr-{N}-test + teardown אוטומטי בסגירת ה-PR.
שינוי מגיע ל-prod אך ורק על ידי מעבר מלא של מחסנית ה-gates המבוימת ולאחר מכן Mike עושה merge ל-PR אל `main`; אין promotion אוטומטי. מודל ה-gate של הגנה-בעומק ב-5 שכבות (lines 619-640): שכבה 1 pre-push מקומי (Husky), שכבה 2 CI ב-push (GH Actions: install/lint/typecheck/check-all/test/build), שכבה 3 branch protection לפני merge (שכבה 2 + Aikido + CodeRabbit ללא בקשות שינוי פתוחות + ב-main: review מאשר אחד מ-Mike), שכבה 4 חזרה-כללית לפני deploy = Pillar 5 Stage 4 מול TEST (Playwright/visual/a11y/perf/DAST/mutation/contract), שכבה 5 אימות לאחר deploy = Pillar 7 (synthetic + burn-rate של SLO + auto-rollback). ה-merge ל-main מגודר: branch protection דורש שכל הבדיקות יהיו ירוקות וגם review מאשר של Mike; no-bypass-for-admins כבוי כך שאפילו Mike משתמש בזרימת ה-PR (חירומים מכוסים על ידי runbook של emergency-override). ב-merge ל-main, Vercel פורס ל-PROD ואימות שלאחר ה-deploy רץ. אספקה הדרגתית (Pillar 7): כל PR משמעותי נשלח מאחורי flag של GrowthBook; canary עולה 1%→10%→50%→100% לאורך 24h (gates של 4h/8h/12h); auto-rollback בכל אחד מ-{שיעור שגיאות פי 2 מה-baseline, p99 פי 2 מה-baseline, כשל synthetic פי 3}; rollback מיידי של קוד דרך החלפת alias ב-Vercel (<30s) + היפוך flag. D30: שלבי ה-ramp מאושרים ידנית על ידי Mike עד שה-Release Agent (Pillar 9 Tier 3) נשלח, אוטומטיים לאחר מכן. עקרון 'לסמוך על ה-gates' (D26, lines 1620-1623): לאחר ההשקה Renovate עושה auto-merge לכל PR ירוק — patch/minor/major כאחד — מפני שכל תפקיד ה-CI/CD הוא להבטיח שכל דבר שעבר merge הוא בטוח; majors שדורשים migration אמיתי נכשלים ב-gates באופן טבעי ונשארים פתוחים. המערכת עולה לאוויר ללקוח משלם ראשון רק כאשר כל 12 ה-pillars נשלחים — אין go-live מדורג (line 2161).
התוכנית ממוקדת-portal (Next.js → Vercel) עבור עמוד השדרה dev→test→main→PROD; מנוע ה-MDR + ה-edr-puller (ה-Docker containers בצד ה-backend על PROD-MDR-01 / TEST-MDR-01) מנוהלים אחרת והתוכנית אינה מגדירה עבורם pipeline מלא של git-promotion. כוונות נעולות מרכזיות:
1) המנוע/ה-puller אינם משתנים מתוך PRs של pbsiem. ה-path_instructions של CodeRabbit (line 717): 'mdr-engine/**, edr-puller/** → contract-drift בלבד; לא מוצעים עריכות קוד (איננו משנים את אלה מתוך PRs של pbsiem).' ה-gate של crypto-contract (lines 731-736) מתייחס ל-src/lib/mdr-crypto.ts (portal), ל-decrypt ב-mdr-engine/src/action-executor.ts, ול-edr-puller/src/decrypt.ts כ-'imports במצב read-only — איננו משנים קבצים אלה'; ה-gate רק מוודא ששלושת ה-decryptors נשארים תואמי-חוזה מול envelope זהב v2 GCM.
2) האופן שבו המנוע מגיע ל-prod = signed-container-pull, ולא Vercel alias. Pillar 6 (supply chain) תת-תוצר 5 (lines 1597, 1609): 'תמונות חתומות-Sigstore עבור docker containers (mdr-engine, edr-puller) נדחפות ל-GHCR. שלב ה-GHCR push ב-CI חותם תמונות דרך cosign sign (keyless OIDC). סקריפטי ה-deployment של המנוע + ה-puller מאמתים חתימות לפני pull.' אימות (line 1660): cosign verify ghcr.io/plan-b-systems/mdr-engine:<sha> מחזיר חתימה keyless תקפה. כך שהמנוע מגיע ל-PROD-MDR-01 על ידי סקריפטי deployment שמושכים תמונות מאומתות-cosign, מאוששות-SLSA-L3, מתוך GHCR.
3) המנוע שזור בתוך ה-pillars החוצי-חתכיים אף שאינו נמצא על עמוד השדרה של ה-Vercel promote: instrumentation של OTel ב-MDR engine + edr-puller עם הפצת W3C TraceContext portal→engine→puller (Pillar 8, lines 1212-1219); GrowthBook SDK ב-portal + engine + puller (Pillar 7 B, lines 1425-1431); gates משותפים של חוזה crypto/rate-limit/audit-log חוצי-רכיבים (Pillar 4 H); אימות attestation חוצה-מוצרים ב-deploy (Pillar 6 #9).
נטו: התוכנית נועלת את נתיב ה-prod של המנוע כ-'תמונה חתומה ב-GHCR → pull מאומת אל ה-box של PROD Hetzner,' שנשמר כן על ידי gates של contract-drift ואימות signature/provenance — אך היא אינה מגדירה promotion בסגנון Vercel של canary/alias-switch או gate של git-merge מ-TEST→PROD עבור המנוע עצמו כפי שהיא עושה עבור ה-portal. זהו הפער המשתמע: עמוד השדרה של promote-to-prod מעוצב לפי ה-portal; המנוע נסמך על משמעת חתימת-containers ולא על זרימת dev→test→main בלחיצת כפתור.
הכוונה הנעולה של כל פילר וההחלטות שעיצבו אותו. המצב הנוכחי של כל אחד נמצא בלשונית מצב נוכחי; מה שחסר נמצא בלשונית פערים.
כל שורה כאן נבדקה מול הריפו האמיתי בתאריך 2026-06-13 בניתוח יריב של 16 סוכנים — לא מהזיכרון, לא מטענות של סשן קודם. זוהי עמודת ה-מציאות עבור כל 14 התחומים.
ה-TEST stack הוא אמיתי וחי: test-siem-api.plan-b.co.il מתרגם ל-178.105.214.148 ומגיש תעודת Let's Encrypt תקפה (openssl Verify return code: 0). pbsiem/docs/01-infrastructure.md:80-97 מתעד את פרויקט ה-Hetzner הנפרד של ה-CI/CD: TEST-siemsys-syslog (132962657 / 178.105.214.148), TEST-MDR-01 (132962660 / 178.105.148.156, 3 קונטיינרים engine+postgres+puller), splitter-syslog (133160347 / 178.105.116.35, rsyslog UDP-514 fan-out ל-PROD 88.198.217.216 וגם ל-TEST), רשת Siem-internal-test (12257988, 10.1.0.0/16), BX11 (584099, u601754.your-storagebox.de). ephemeral לכל PR מחווט: .github/workflows/ephemeral-env.yml קורא ל-cicd-system ephemeral-env.yml@main (Neon branch מהורה "production", override של Vercel preview) וגם ל-pr-backend-stack.yml@main עם teardown בסגירת ה-PR (lines 19-56). פריסת TEST של המערכת המלאה מחווטת (סותר את ה-open gap של ה-runbook): הכותרת של .github/workflows/stage4.yml "THE RELEASE-CERTAINTY CORE (S5, D-4=Option C)" בונה מחדש את mdr-engine+edr-puller ב-PR SHA דרך docker-build, פורסת את שניהם ל-TEST stack המשותף דרך siem-deploy.yml@main עם required-env-marker:TEST + health/RestartCount gate (lines 85-125), עושה migrate ל-MDR-PG, מסנכרנת את ה-receiver, ואז מריצה e2e/dast/perf/visual/link-crawl מול ה-preview האמיתי. prod-isolation gate מחווט: scripts/check-all.mjs:52 מפעיל את check-prod-isolation.mjs. ENGINE PROD PROMOTE: אין. prod-deploy.yml + prod-promote.yml הם VERCEL-PORTAL-ONLY (prod-promote.yml:48-64 קורא ל-vercel-promote.yml כדי להזיז את ה-alias siemsys.plan-b.systems; ל-prod-deploy.yml אין אף צעד של engine/docker/ssh/hetzner). siem-deploy.yml נקרא רק על ידי stage4.yml + rollback-drill.yml (שניהם TEST) — לעולם לא עבור prod; הכותרת שלו עצמו מפרסמת מצב "deploy to PROD host (env-gated)" שאף קורא לא משתמש בו.
אומת ב-pbsiem (plan-b-systems/pbsiem, clone מקומי). (1) Rotation cron נשלח (SHIPPED) ומחווט (WIRED): .github/workflows/rotate-secrets.yml:42 cron '19 20 * * 5' + workflow_dispatch; scripts/rotate-secrets.sh מבצע rotation לבדיוק שני סודות — OPENSEARCH_PROXY_KEY (line 122/149-150) וסיסמת ה-PG של mdr_admin (line 116-117, ALTER USER ב-206-210). גילוי hosts מתבצע דרך GET על src/app/api/cron/rotation-targets/route.ts (route.ts קיים, 1884 bytes) באמצעות CRON_SECRET bearer. אין rotation עבור MDR_ENCRYPTION_KEY, bearer-token, PAT או GrowthBook. (2) "No human sees prod credentials" מיושר (ALIGNED) עבור 2 הסודות שעוברים rotation: rotate-secrets.sh:355-359 מתעד sha256 fingerprints (16 התווים הראשונים) בלבד; קובץ הערכים בטקסט גלוי עובר shred בכל נתיב יציאה (trap EXIT INT TERM, line 135; אומת ב-361-363). (3) ה-proxy getSecret() חסר (MISSING): אין src/lib/secrets/get-secret.ts (התיקייה נעדרת); ההתאמות היחידות של getSecret הן helpers מקומיים לא קשורים — src/lib/trusted-browser.ts:21 (מפתח חתימת HMAC) ו-src/lib/twilio-callback-token.ts:32 — ולא ה-proxy המבוקר המרכזי. (4) audit של SECRET_ACCESS חסר (MISSING): grep "SECRET_ACCESS" על פני pbsiem מחזיר אפס תוצאות; מודל AuditLog קיים (prisma/schema.prisma:586) אך action הוא מחרוזת חופשית ללא שימוש ב-secret-access. (5) OIDC נדחה כראוי (DEFERRED): VERCEL_TOKEN ארוך-טווח עדיין מאוזכר על ידי 8 workflows (ephemeral-env, post-deploy-verify, prod-deploy, prod-promote, prod-verify, rotate-secrets, stage4, visual-baselines). (6) Per-env scoping חלקי (PARTIAL): מילת המפתח environment של GH קיימת רק ב-prod-deploy.yml; סקריפט ה-rotation שומר על הפרדה בין creds של preview/TEST לבין PROD (lines ~154). (7) docs/secrets.md חסר (MISSING).
מחווט ורץ ירוק (green) היום (אומת ב-2026-06-14). cicd-system מספק workflows מסוג `workflow_call` לשימוש חוזר — `.github/workflows/ephemeral-env.yml` (יצירה/שימוש חוזר ב-Neon branch עם endpoint מסוג read_write, hard-fail על parent שגוי, שער EPHEMERAL_PAUSED, retry על connection-uri, משתני Vercel preview, seed, תגובת PR, ו-teardown מלא בסגירת PR) ו-`pr-backend-stack.yml` (engine+puller+postgres per-PR ב-docker-compose על host של pool ב-TEST 178.105.148.156, סיסמת PG דטרמיניסטית ב-HMAC, pool eviction/MAX_STACKS, image GC). ה-`.github/workflows/ephemeral-env.yml:19` של pbsiem קורא ל-reusable של cicd ב-@main עם neon-project-id autumn-thunder-97996128, neon-parent-branch=production, seed-script scripts/qa-seed.ts, pr-stack-host. ה-`stage4.yml` של pbsiem פורס engine/puller/receiver ל-TEST + מקים את ה-backend stack של ה-PR עצמו + מריץ e2e/dast/perf/visual/links מול ה-Vercel preview האמיתי של ה-PR. Live: `gh run list` מראה הצלחת ephemeral-env (14-44s) והצלחת stage4 (10-13m) ב-2026-06-14; כל ה-jobs של stage4 ב-PR #78 ירוקים כולל deploy-engine/deploy-puller/pr-stack/pr-services/e2e/links. Crons קיימים: `src/app/api/cron/ephemeral-env-gc/route.ts` (Neon בלבד, 03:00) ו-`ephemeral-env-cost/route.ts` (ספירת Neon branch, 04:00, dual-auth) ב-pbsiem vercel.json:192/196. Admin UI ב-`src/app/admin/ephemeral-envs/page.tsx` (לא ב-path (admin)/admin/infra שבתוכנית).
אומת על pbsiem origin/main + gh + cicd-system. Layer 1 קיים: .husky/pre-commit=`npx lint-staged`, .husky/pre-push=`npm run prepush`, .nvmrc=22.16.0 (התוכנית אמרה 22.11.0), husky^9.1.7 + lint-staged^15.4.3, ה-config של lint-staged תואם, engines>=22.11.0. אבל `prepush`=`npm run check && npm run typecheck && npm run lint && npm run test:unit` — משתמש ב-`npm run lint` (לא lint:strict) + `test:unit` (לא ה-test המלא); מתבדל מהתוכנית וגם מ-README L28 שטוען `--max-warnings 0` + `vitest run`. Layer 2 נבנה אחרת (BUILT-DIFFERENT): pbsiem/.github/workflows/ci.yml קורא ל-reusable cicd-system/.github/workflows/ci.yml@main כ-job משולב יחיד (required check בודד `ci / CI`, steps סדרתיים), ולא כ-6 checks נפרדים; pbsiem מעביר `lint-command: "npm run lint"` שדורס את ברירת המחדל `lint:strict` של ה-reusable — lint-warnings-as-errors לא נאכף ב-CI. check-all.mjs מריץ 13 lints + מגלה אוטומטית scripts/check-contracts/*.mjs (crypto-envelope.mjs קיים) — D17 framework נבנה+מחווט+נאכף (BUILT+WIRED+ENFORCED) (aligned). Aikido חסר/הוחלף: cicd-system/.github/workflows/security-scan.yml L1+L3 = "Security Scan (Semgrep + Trivy) … Replaces Aikido (D54 superseded)"; מריץ Semgrep+Trivy בלבד, ללא step של Aikido; pbsiem security-scan.yml enforce:true חוסם CRITICAL,HIGH עם allowlist. CodeRabbit: האפליקציה אכן מותקנת (coderabbitai כ-reviewer; PR#78 check `CodeRabbit pass / Review skipped`) אבל ADVISORY — .coderabbit.yaml לא קיים ב-origin/main (הוסר ב-commit fb8a2fb "remove CodeRabbit — can't downgrade to free tier"), request_changes_workflow מוגדר כברירת מחדל FALSE, לא required check. Branch protection (gh api): dev strict=False, 10 checks {ci/CI, impact, dast, e2e, perf, verify, visual, Vercel, links, security-scan/scan}, 0 reviews, enforce_admins=False; main strict=True, רק {ci/CI}, req_approvals=0, enforce_admins=TRUE, force=off. אף אחד מהענפים לא מציג את Lint/Typecheck/Test/Build/Aikido/CodeRabbit כ-checks נפרדים; main עם 0 approvals (התוכנית רצתה 1) + enforce_admins ON (התוכנית רצתה OFF). תיעוד: README Contributing 5-layer + --no-verify→docs/runbooks/prod-emergency-override.md; docs/runbooks/ci-gate-failures.md קיים; ARCHITECTURE.md L133 עדיין אומר "6 parallel jobs" + "Aikido + CodeRabbit" pre-merge — מיושן (STALE).
VERIFIED. ב-cicd-system קיימים reusable workflows לכל מערך הבדיקות (.github/workflows/: playwright-e2e.yml, visual-regression.yml, perf-budget.yml [Lighthouse+k6], a11y-i18n.yml [axe-core + i18n parity], mutation-test.yml [Stryker], dast-zap.yml, link-crawl.yml). pbsiem stage4.yml (lines 208-269) מחווט רק 5 שערים אל ה-run הייעודי לכל PR: e2e, dast, perf, visual, links — בנוסף ל-deploy אמיתי של full-system TEST (build-engine/build-puller/migrate-test-db/deploy-engine/deploy-puller/deploy-receiver) וגם backend stack מבודד לכל PR (pr-stack/pr-db/pr-services עם synthetic-seed.ts), שחורגים מעבר להיקף ה-\"ephemeral env\" שבתוכנית. ה-required contexts של ה-branch protection ב-dev (gh api): ci/CI, impact, dast, e2e, perf, verify, visual, Vercel, links, security-scan — אומתו כחיים וירוקים על PRs אמיתיים (ריצות stage4.yml בתאריכים 2026-06-13/14 = success). אבל: (1) e2e מריץ רק את tests/e2e/dev-environment.spec.ts (stage4.yml:218) מתוך 90 קבצי spec — לא את מערך 86 ה-specs; הערה ב-workflow מודה \"broaden... once the ephemeral DB seeding is wired deterministically.\" (2) visual מכסה 3 עמודים login/pricing/status, EN בלבד, baselines tests/e2e/visual-regression.spec.ts-snapshots/*-chromium-linux.png — לא 24 EN+HE. (3) perf = lighthouse-urls \"/,/login\", configs/lighthouse-budget.json budget סטטי, k6-script-path ריק (אין k6, אין rolling-from-main baseline). (4) i18n-parity.yml קורא ל-a11y-i18n.yml אך רק את ה-job של i18n, מגודר ל-paths src/lib/i18n/** ולא ב-branch protection (הריצה האחרונה 2026-06-10 ירוקה). (5) job של axe-core accessibility: לעולם לא נקרא על ידי pbsiem. (6) mutation-test.yml: לעולם לא נקרא על ידי pbsiem. (7) cross-tenant: tests/e2e/security/ לא קיים; grep cross-tenant = 0 hits. (8) zod-to-openapi/oasdiff: 0 hits בשום מקום. (9) AICostLedger: לא קיים ב-prisma/. (10) migration safety: אין check-migration-safety.mjs; קיים רק scripts/check-contracts/crypto-envelope.mjs (stage4 מבצע prisma db push כן והוגן ללא --accept-data-loss, תחליף חלקי). GAP A מאומת: ה-required check של ה-branch protection ב-main הוא רק \"ci / CI\" — מערך Stage-4 אינו רץ מחדש בקידום (promotion) dev→main.
אומת בקוד וב-gh חי: (1) SBOM — ה-reusable sbom.yml קיים (cicd-system/.github/workflows/sbom.yml) והוא מחווט: pbsiem/.github/workflows/sbom.yml קורא לו @main ב-push ל-main; ריצות חיות מצליחות (הריצה האחרונה 2026-06-14T01:06 success). אך הוא סוטה מהתוכנית: מעלה artifact עם retention של 90-DAY (sbom.yml:68 retention-days: 90), לא מצורף לאף GH Release, לא נדחף ל-Storage Box לתמיד — והוא מבצע SBOM רק על תלויות npm של ה-portal, לעולם לא על ה-container images. (2) SLSA L3 — ה-reusable slsa-provenance.yml קיים (cicd-system) ומנוסח היטב (attest-build-provenance@v2, gh attestation verify), אך יש לו אפס callers: 404 ל-workflow ב-pbsiem, וחיפוש קוד ארגוני אחר slsa-provenance.yml@main לא מחזיר דבר. שלב צירוף-ה-release שלו (slsa-provenance.yml:54 `if: github.event_name == 'release'`) לעולם לא יכול לפעול — ל-pbsiem אין releases כלל (gh release list ריק). => SLSA = נבנה-אך-לא-מחווט. (3) חתימת image עם Sigstore — docker-build.yml (cicd-system) בונה+דוחף את mdr-engine/edr-puller ל-GHCR ללא שום שלב cosign sign (docker-build.yml:74-95); pbsiem/docker-build.yml קורא לו ללא שינוי. siem-deploy.yml מבצע pull/ssh-load של images ללא שום cosign verify (siem-deploy.yml:109-191). grep אחר "cosign" ברחבי cicd-system (ללא node_modules) פוגע רק ב-docs וב-findings route, לעולם לא ב-workflow. => חתימת image ו-verify = חסרים. (4) SLSA Source Track L1 — מיושר: branch protection נאכף (בהתאם ליעד L1 שנבחר בתוכנית, שמסופק על-ידי branch protection בלבד; signed commits נדחו כראוי). (5) Renovate / dep-audit cron — חסרים: אין .renovaterc.json ב-pbsiem או ב-cicd-system; אין workflow של osv-scanner/npm-audit-cron באף אחד מהם. (6) ציון סיכון לשרשרת אספקה — חסר: אין pane של Grafana/dashboard; מחרוזת ה"provenance" היחידה ב-dashboard היא בדיקת matchesMainTip של promotion-PR ב-Vercel (promotions/page.tsx:201,466), שאינה קשורה ל-SLSA/cosign. (7) docs/supply-chain.md — חסר (אין קובץ כזה). הערה: security-scan.yml:3 אומר "Replaces Aikido (D54 superseded)" — Aikido למעשה OUT בקוד (Semgrep+Trivy בלבד), בסתירה לטענת "Aikido Back IN" שב-build.py:169.
מנגנון ה-canary/flags הוא BUILT-BUT-NOT-WIRED (בנוי אך לא מחובר), וכמה רכיבים פשוט חסרים. (1) cicd-system/.github/workflows/canary-gate.yml קיים עם לוגיקה אמיתית: ramp של rollout-coverage דרך GrowthBook PUT /api/v1/features לפי לוח הזמנים הדיפולטי של D28 (line 39), בדיקות error-rate-vs-baseline בחלון gate של 5 דקות, ובעת breach ביצוע flag-disable ב-GrowthBook + כשל ה-workflow (lines 128-219). אבל מדובר ב-reusable מסוג workflow_call שלא מופעל על ידי שום דבר אמיתי — grep מראה שההפניה היחידה היא configs/example-callers/canary-caller.yml; אף workflow של pbsiem לא מפנה ל-canary/growthbook/feature-flag (grep של pbsiem/.github/workflows החזיר ריק). (2) GrowthBook לא deployed: configs/growthbook-setup.md הוא doc בלבד (קטע docker-compose בתוך markdown), וכל שלושת ה-hosts המתוכננים (gb.plan-b.co.il, growthbook.plan-b.co.il, gb-api...:3100) מחזירים connection failure (curl 000). אין SDK של @growthbook/growthbook ב-portal/engine/puller. (3) אין scripts/rollback-prod.mjs (find scripts/deploy החזיר רק scripts לא קשורים). פרימיטיב ה-Vercel-alias נמצא במקום זאת ב-pbsiem prod-promote.yml -> cicd vercel-promote.yml, ומשמש כ-HUMAN PROMOTE (repoint של alias, ללא ramp); ו-rollback מסוג Vercel promote-previous מוטמע inline ב-post-deploy-verify.yml (lines 130-183) אך הוא SEALED OFF ב-production (auto-rollback דיפולט false; lines 29-33, 191 אוכפים verify+page-Mike, ולעולם לא auto-rollback ב-prod לפי S6). (4) אין /api/cron/canary-ramp, אין /api/cron/burn-rate-rollback. (5) אין UI של /admin/canary או /admin/deploy/rollback ב-pbsiem (grep ריק). (6) אין Migration Review Agent ואין אכיפת expand-contract בשום מקום — cicd change-impact.yml רק מחיל LABEL מסוג "schema-changing" על נתיבי prisma/schema ו-/migrations/ (lines 71-76), ללא prisma migrate diff, ללא חסימת ALTER, ללא הצעת expand-contract; pbsiem change-impact.yml אפילו לא מפנה ל-prisma. (7) אין סוגי אירועי AuditLog של canary (grep ל-CANARY_RAMP/CANARY_ROLLBACK/PROD_DEPLOY_ROLLBACK ב-pbsiem החזיר ריק). ה-rollback-drill.yml שהוא TEST-only (שחזור container, assert של PLANB_ENV=TEST) כן הופעל (S6: restore בתוך כ-1s/21s) — זה הדבר היחיד הסמוך ל-progressive-delivery שהוכח כעובד, וזהו restore של engine container, לא canary או alias rollback.
מאומת. ה-stack של observability ב-TEST אכן קיים ורץ: observability/docker-compose.yml (Prometheus v2.55.1, Alertmanager v0.27.0, Grafana 11.3.1, node-exporter, blackbox v0.25.0, otel-collector-contrib 0.114.0, Caddy) על ה-Hetzner Build-Runner; grafana.plan-b.co.il/api/health מחזיר 200 {database:ok, version 11.3.1} — אומת חי באמצעות curl. slo/slo-definitions.json מחזיק את ששת ה-SLOs; observability/generate-slo-alerts.mjs מרנדר את observability/prometheus/rules/slo-alerts.yml (11 alerts). אבל: (1) כללי ה-alert האלה שואלים את http_requests_total{slo=...} ו-http_request_duration_seconds_bucket{slo=...} — metrics ששום דבר לא פולט. pbsiem/src/instrumentation.ts:8-15 רק מייבא את sentry.server/edge config; אין שום @vercel/otel, אין OTLP exporter, אין registerOTel, ואין W3C propagation ב-portal/engine/puller (grep של pbsiem src = 0 hits מחוץ ל-node_modules). ה-traces pipeline של ה-collector מייצא אל 'debug' (collector.yaml:36, נזרק). לכן ה-burn-rate alerts לעולם לא יכולים לירות על תעבורה אמיתית — הם יושבים על baseline של 0. (2) ה-SLOs של ה-dashboard הם סט שונה: dashboard/src/lib/slo-compute.ts:65-93 מחשב 'CI success rate', 'CI p90 duration', 'Deployment success rate' מתוך שורות ה-build/deployment של ה-dashboard עצמו — לא ששת ה-MDR SLOs. dashboard/src/app/api/slo/route.ts מגיש אותם. (3) שיוך עלויות: אפס client_id או feature_flag labels בכל מקום ב-observability/ (grep = ללא התאמות). dashboard /api/metrics (route.ts) מייצא רק cicd_* platform metrics; ה-cost metric היחיד הוא cicd_weekly_estimated_cost_eur מתוך EphemeralEnvCost (הוצאת ephemeral-env), לא per-customer/per-feature. אין שום קוד cost-exporter (Glob **/cost-exporter* = none). (4) Sentry: tracesSampleRate 0.05 ב-prod (לא 0.1) ו-Session Replay מושבת במכוון (sentry.client.config.ts:11-16) — אין client_id ב-beforeSend. (5) Better Uptime: לא משולב; 'synthetic monitoring' = blackbox probes נגד 2 URLs בלבד (prometheus.yml:51-53), לא 6 ב-1-min דרך Better Uptime. (6) Alertmanager מנתב אל ALERT_WEBHOOK_URL = ה-webhook של dashboard ה-CI/CD (כותב שורות audit מסוג ALERTMANAGER_WEBHOOK), לא אל משטח ה-SVC-HEALTH /admin/infra/service-health של pbsiem שהתוכנית מציינת בשמו. (7) D38 PROD-obs-stack: לא הוקצה (TEST-only).
קיים framework אמיתי וקוהרנטי לסוכנים תחת cicd-system/agents/, אבל שום דבר לא מפעיל אותו. הקבצים: framework/types.ts (AgentTier enum 0-4, AgentSpec, TrustThreshold), framework/runtime.ts (מפת TIER_PERMISSIONS, createAgentRuntime עם hooks ידניים של pre/post-tool-use + gating לפי tier+allowlist+cost, loadAgentSpec/listAgentSpecs), framework/telemetry.ts (recordInvocation→JSON מקומי, checkEarnedTrust, TRUST_THRESHOLDS), framework/cost-guard.ts (MODEL_PRICING, DEFAULT_CAPS לכל-run 0.5/לכל-PR 5/לכל-repo-mo 50, checkCostBudget/recordCost). 10 specs ב-agents/specs/*.json (migration-review T0, pii-detection T0, cost-anomaly T0, ci-triage T1, doc-drift T1, visual-regression-triage T1, test-generation T1, dependency-update T1, oncall-investigation T2, release T3). WIRING: grep אחר createAgentRuntime/loadAgentSpec/runAgent/agents/framework מחזיר אך ורק את docs/cicd-platform-build-plan.md, agents/README.md, ואת runtime.ts/index.ts של ה-framework עצמו — אפס callers ב-dashboard/, ב-.github/workflows/ (אף אחד מ-~22 ה-workflows לא מפנה אליו), ב-scripts/, או ב-pbsiem. אין package.json/tsconfig בשורש — קוד ה-TS של ה-framework אינו מהודר לתוך אף build target; אין *.test.* ל-framework. D39 מופר: אין dependency של Claude Agent SDK בשום מקום; allowedTools הוא string[] רגיל של JSON שנבדק ידנית (runtime.ts:201-228), ולא allowedTools של ה-SDK בשכבת ה-API; ה-hooks מקודדים ידנית, ולא PreToolUse/PostToolOutput של ה-SDK. מספרי ה-earned-trust של D41 תואמים (telemetry.ts:30-34) אבל אין שום שדה SHADOW/LIVE בשום מקום ו-checkEarnedTrust רק מדווח על זכאות — לעולם לא מקדם אוטומטית (README:119 \"Promotion is not automatic\"); כמו כן overrideRate מחושב כ\"אדם דרס DENIAL של tier\" (telemetry.ts:96), ולא כ\"אדם עשה משהו שונה ממה שהסוכן הציע\" של התוכנית — מטריקה שונה. D42 מופר: ה-telemetry הוא JSON מקומי ב-gitignore (agents/telemetry/*.json), ולא OTel/Prometheus/Grafana. חסר לחלוטין: ערוץ <untrusted> (grep: רק ב-docs), graph cap בין סוכנים / cascade cost (grep: רק ב-docs), golden-input suites (אין), post-mortem חודשי + agent-off-day (אין), מודלי AgentTrustState + AICostLedger מתמשכים (נעדרים מ-schema של pbsiem — מודלי ה\"trust\" היחידים הם TrustedDevice/Persona-4 trust-period, לא קשורים), התאמה ל-CSA framework (הטרמינולוגיה לא נוכחת). 4 הסוכנים הקיימים אינם עברו retrofit/הוכרזו בתור tier. הדבר היחיד הצמוד-לצי-סוכנים שבאמת WIRED הוא dashboard/src/lib/promotion-verdict.ts: שני \"verdict reviewers\" מבוססי-LLM בעלי אופי מייעץ (Anthropic claude-sonnet-4-6 + OpenAI gpt-4o) שנקראים מ-/api/promotions/verdict, /api/promotions, ו-/api/cron/promotion-notify, נשמרים ב-AuditLog (action=AI_VERDICT), ומוצגים בחלונית ה-Promotions / בלוח התוצאות — אבל זו מימוש עצמאי מבוסס raw-HTTP: הוא אינו מייבא את agents/framework, אין לו tier runtime, אין cost guard, אין allowedTools, והוא מזין את הודעת ה-commit אל ה-prompt ללא עטיפת <untrusted> (promotion-verdict.ts:35-68). הוא מייעץ/shadow בלבד (לעולם לא חוסם את הכפתור, Safety Rule 5).
חלקי (PARTIAL). מה שקיים ומחווט: (1) C5 — תיוג אוטומטי של change-impact פעיל (LIVE): ה-reusable workflow ב-cicd-system/.github/workflows/change-impact.yml מחיל את כל 5 ה-labels מהתוכנית (auth-touching/schema-changing/customer-facing/security-relevant/pii-touching, שורות 62-103) כולל סריקת PII מבוססת-תוכן על ה-diff (146-162); ב-pbsiem/.github/workflows/change-impact.yml:17 קוראים לו עם @main על pull_request אל dev/main; gh מציג ריצות מוצלחות מ-2026-06-14 (run 27485718386, 14s). (2) קיים מאגר AuditLog בדאשבורד (cicd-system/dashboard/prisma/schema.prisma:265-280) שנכתב אליו באופן פעיל — אבל רק עבור פעולות control-plane של הדאשבורד: promotions/dispatch (controls/dispatch/route.ts:84, promotions/dispatch:72, merge-pr:86, promotion-notify:106), סטטוס ממצא אבטחה (security/findings/[id]/status:59, repair-queue:127), webhooks/autofix:58, webhooks/alertmanager:45. (3) ל-MDR יש מודל AuditLog משלו (pbsiem/prisma/schema.prisma:586-598: admin_email/action/target/details/ip/created_at) + gate בשם check-audit-coverage.mjs + gate בשם check-pci-body-logging.mjs, שניהם ב-check-all.mjs (שורות 35,41). שבור/לא-מחווט: (a) C1 — האודיט בשכבת ה-pipeline הוא WRITE-TO-VOID (כתיבה לתהום): cicd-system/.github/workflows/audit-log.yml שולח POST של payload מותאם (repo/workflow/run_id/changed_files/duration_seconds) עם header X-GitHub-Event:workflow_run אל /api/webhooks/github, אך dashboard/src/app/api/webhooks/github/route.ts:55-64 דורש payload.repository ומחזיר HTTP 400 "Invalid payload" לצורה הזו; ה-workflow בולע כל תגובה שאינה 2xx כ-::warning:: (audit-log.yml:91) ובכל זאת "מצליח". טבלת ה-Build מאוכלסת על-ידי אירועי workflow_run הנייטיביים של ה-GitHub App הארגוני, ולא על-ידי audit-log.yml — והנתונים שלה לעולם אינם נשמרים לשום מאגר אודיט. חסר לחלוטין (מאומת כנעדר בשני ה-repos באמצעות find): תיקיית compliance/; C2 soc2-control-map.yaml; C3 pci-control-map.yaml; D47 pci-tranzila-scope-letter.pdf; C4 docs/data-flows.md; קונפיגורציות auditd של C6; דוחות חתומים של C7 תחת compliance/drills/; C8 /api/customer/audit-export (אין endpoint של audit-export בשום מקום; קיימים רק admin/audit + admin clients export-request); אכיפת retention של C9 (אין crons ל-7yr/5yr, אין שמירת append-only או guard לאי-שינוי על אף אחד משני מודלי ה-AuditLog); C10 docs/compliance.md. אין כלל artifacts של מיפוי SOC 2 / PCI / GDPR.
פריט ה-DR היחיד שבתחום הוא BUILT-BUT-NOT-WIRED, מזויף חלקית, וסוטה ארכיטקטונית מ-D1. (1) הקובץ .github/workflows/backup-restore-verify.yml קיים על main של plan-b-systems/cicd-system אך הוא `on: workflow_call` בלבד (lines 3-4). הקומיט 2dd43a8 ("fix: remove schedule trigger from backup-restore-verify workflow", 2026-05-28) מחק את `schedule: cron '0 3 * * *'` (שהיה בראש הקובץ) מכיוון שהוא המשיך להיכשל על cicd-system עצמו (אין NEON_API_KEY) — מתוך כוונה מוצהרת שריפו צרכן (consumer repo) יקרא לו. אף ריפו צרכן מעולם לא עשה זאת: grep לרוחב pbsiem ו-cicd-system לא מוצא אף קורא (callers) — רק אזכורי פרוזה ב-ARCHITECTURE-CI.md. 8 הריצות האחרונות ב-GH כולן `failure` עם 0s (כשלי self-repo שמלפני ההסרה). (2) ה-workflow משחזר ענף Neon לתוך ענף Neon EPHEMERAL (backup-restore-verify.yml:82-126), ולא "גיבוי PROD של אתמול INTO TEST" כפי ש-D1 דורש — הוא מעולם לא נוגע בארטיפקט גיבוי אמיתי או ב-stack של TEST. (3) שלב "Verify row counts within tolerance" (lines 169-193) הוא NO-OP: הוא כותב `deviation_ok=true` ללא תנאי בין אם קיים מידע ובין אם לא — בדיקת ה-tolerance המפורסמת לא עושה כלום. (4) הוא מכסה רק את ה-DB של פורטל Neon; מידע המוצר האמיתי של mdr (PostgreSQL על PROD-MDR-01, infra/prod-mdr-01/README.md) אינו ב-workflow הזה. (5) הקובץ docs/cicd-platform-build-plan.md:216 מציין את מימוש ה-cron-route שמעולם לא נבנה — src/app/api/cron/backup-restore-verify/route.ts אינו קיים. (6) גיבוי ה-PG היומי של MDR בצד ה-host (02:30 -> Storage Box) הנטען ב-build.py:872 אינו ניתן לאימות באף ארטיפקט ריפו שעבר commit — תיקיית infra/prod-mdr-01/backups/ מכילה רק pgdump ידני אחד מאפריל שלפני stage1; אף יחידת cron/סקריפט לא עבר commit. (7) ה"drill" החי הקרוב ביותר הוא pbsiem .github/workflows/rollback-drill.yml (פעיל, אך workflow_dispatch בלבד) — חזרה (rehearsal) של שחזור קונטיינר ב-Pillar 7 על TEST, ולא ה-DR D4 quarterly drill (אין מדידת RTO/RPO, אין דוח חתום, לא מתוזמן). (8) לא קיימים מסמכי DR (docs/dr.md, docs/runbooks/dr-drill.md, docs/runbooks/failover-decision.md כולם נעדרים) — אך אלה מחוץ לתחום על פי D49.
M10 ממומש חלקית אך עם סטייה: מאגר ה-reusable-workflow קיים בתור plan-b-systems/cicd-system (27 reusable workflows תחת .github/workflows/) ובאמת נצרך — ל-pbsiem יש 30 הפניות uses: plan-b-systems/cicd-system/.github/workflows/*.yml@main (ci.yml:18, ephemeral-env.yml:19/48, post-deploy-verify.yml:26/53, prod-promote.yml:48/72, ...) ו-vaughnblades צורך 3 (ci/sbom/security-scan, כולם @main). אבל: (1) המאגר הוא PRIVATE (gh repo list: plan-b-systems/cicd-system ... private), בניגוד ל-D51 PUBLIC; (2) לא קיים מאגר plan-b-systems/ci-templates (gh: \"Could not resolve to a Repository\"); (3) אין תגיות SemVer / @v1.0.0 — ה-consumers מבצעים pin ל-@main (30/30 הפניות ב-pbsiem, 0 SHA-pins, 0 @v pins), בניגוד ישיר לכלל \"never @main\" של התוכנית למרות ש-README.md:18/24 מתעד \"@<sha>... Pin by SHA, not branch. Renovate auto-updates\" (המשמעת כתובה, לא מיושמת). vaughnblades (39 PRs ממוזגים, מוצר אמיתי וחי) הוא דה-פקטו מבחן ה-conformance, שעבר onboarding מ-cicd-system. M6 anonymizer: קיים רק scripts/anonymize-seed.mjs — scrubber מבוסס REGEX על stream של SQL מעל טקסט pg_dump (emails/phones/IPs/cards), ולא ספריית ה-scrub(row, schema) מודעת-הסכמה שבתוכנית; אין wiring ל-weekly-TEST-refresh; יתום אל מול הלוגיקה הנפרדת של S2b/S2c scripts/dev-scrub. M1 services.yaml: ABSENT בשני המאגרים (Glob: no files) למרות שמסגרת ה-agents + ה-specs שהוא אמור להזין קיימים (agents/specs/cost-anomaly.json, oncall-investigation.json, doc-drift.json) — לאותם agents אין קטלוג לקרוא ממנו (מחמיר את הסטייה 'מסגרת agent נבנתה אך לא חוברה לכלום'). M2 Terraform: ZERO קבצי .tf בשום מקום (Glob: no files); התשתית מנוהלת ידנית. M3 runbooks-as-code: docs/runbooks/*.md קיימים בשני המאגרים אך בפורמט חופשי (auto-fix.md, link-crawl.md, ...) ללא סכמת-סקציות נדרשת וללא ולידציית CI. M4 chaos: אין סקריפטים של chaos; ההתאמות היחידות ל-'chaos' הן ב-ARCHITECTURE-CI.md המיושן של pbsiem (Litmus/Stage-7 staging — עיצוב אחר שלא נבנה) ובמסמך ה-plan-mirror. M5/M7/M8/M9: לא נבנו — אין status-page-to-SLO binding, אין מדיניות X-Deprecated/API-versioning, אין משטח export קוהרנטי ללקוח, אין שער סריקת-PII מעל fixtures/visual baselines (security-scan.yml:85 מריץ Trivy vuln,secret,misconfig שתופס secrets אגב אך אינו שער ה-PII המתוכנן ל-fixture/screenshot).
טופולוגיה: מסמכי pbsiem וה-runbook מאשרים 3 שרתי TEST + פרויקט plan-b-test (לא אומת מחדש כפעיל בסשן הזה). הזרימה בנויה אחרת מהתוכנית: (1) סביבת ephemeral לכל PR = Vercel preview של ה-portal + ענף Neon בלבד (ephemeral-env.yml); (2) stage4.yml מדפלוי את engine+puller ל-TEST stack המשותף דרך siem-deploy.yml עם required-env-marker:TEST + TEST_MDR_DEPLOY_HOST (stage4.yml:89-127) — משותף, מסודר סדרתית בין PRs, לא מבודד לכל PR; (3) שער ה-merge-to-main האנושי של התוכנית מוחלף ב-promotion-autopilot.yml (משתנה PROMOTION_AUTOPILOT = on, אומת כפעיל) שפותח אוטומטית PRs של promotion מ-dev→main + PRs של "fold" וממזג אותם אוטומטית ברגע ש-certify ירוק — מסיר את "שלב ה-merge האנושי שבאמצע" (promotion-autopilot.yml:8-9); (4) PROD מושג דרך prod-deploy.yml (push ל-main → vercel-gated-deploy, staging artifact, ללא הזזת alias) ואז קליק Promote אנושי נפרד (prod-promote.yml מופעל ע"י ה-dashboard /api/promotions/dispatch/route.ts) שמזיז את ה-alias של Vercel בלבד. חי: ריצת prod-deploy 27484292775 (PR #77) + Promote 27484423128 שתיהן הצליחו 2026-06-14; fold PR #76 פעיל. נתיב ה-PROD של המנוע חסר: siem-deploy.yml נקרא רק ע"י stage4.yml (TEST) ו-rollback-drill.yml — אין שום קורא שמדפלוי את engine/puller ל-PROD-MDR-01. docker-build.yml דוחף ל-GHCR אבל ל-docker-build הניתן לשימוש חוזר של cicd-system אין שלב cosign sign, אין SLSA attestation; אפס קוראים של slsa-provenance/attest-build-provenance ב-pbsiem; siem-deploy.yml לא מבצע cosign-verify לפני pull. כך שספינת ה-PROD של המנוע מהתוכנית (signed-image→verified-pull) לא קיימת; המנוע מגיע אי-פעם רק ל-TEST. רענון נתוני ה-staging חסר: אין anonymize-seed.mjs / אין ספריית scrub(row,schema) / אין verify-dev-scrub / אין cron של רענון שבועי ב-pbsiem (ה-DEV DB סוטה → flake בשערים, ה-P0 שהבעלים נתקל בו). שער prod-isolation תואם-אך-עבר-שינוי-שם: scripts/check-prod-isolation.mjs (מונע-קונפיגורציה דרך audit-prod-isolation.config.yaml) מחווט אל scripts/check-all.mjs:52.
אומת ב-pbsiem/.github/workflows: prod-deploy.yml מממש verify-BEFORE-promote (header L1-28: "production alias is STRUCTURALLY unreachable by an unverified artifact"; קורא ל-vercel-gated-deploy.yml@main עם promote-after-verify:true, auto-promote:false, prod-alias siemsys.plan-b.systems) ומחנה ארטיפקט staging ("READY TO PROMOTE", הרצה מוכחת 27328491331 לפי cicd vercel-gated-deploy.yml:8). prod-promote.yml הוא workflow_dispatch נפרד שהוא שער אנושי המאמת מחדש provenance, מפנה מחדש את ה-alias, ואז סורק (crawls) את ה-alias החי (prod-promote.yml:6-72) — Gap B סגור בקוד. stage4.yml (L41-126) אכן בונה mdr-engine + edr-puller ב-PR SHA, מחיל את סכמת ה-TEST DB, ופורס את שניהם ל-TEST-MDR-01 דרך siem-deploy.yml@main עם PLANB_ENV/rollback מגודר-בריאות (health-gated) — full-system per-PR TEST deploy מחווט (סותר את טענת ה-"open" שבלשונית ה-gaps). certify.yml קיים על PRs ל-main, מגדר מכנית על protected_merge + introducing-PR gates, ומריץ AI CERTIFY/REFUSE רק כאשר ANTHROPIC_API_KEY קיים (certify.yml:145-161) — רדום בכוונת תכן. promotion-autopilot.yml פותח PR מ-dev→main ו-ARMS את ה-auto-merge של GitHub ברגע ש-certify=CERTIFY + הבדיקות ירוקות (L92-137) — מסלול promotion אוטומטי שהתוכנית אוסרת. חסר במסלול הקריטי: (a) אין engine→PROD deploy/promote workflow — docker-build.yml בונה image-ים של engine/puller ב-push ל-main אך יעדי ה-DEPLOY היחידים של ה-engine הם TEST (stage4, rollback-drill); PROD-MDR-01 (178.104.172.142) מופיע רק ב-known-hosts של rotate-secrets, לעולם לא כיעד פריסה. (b) אין ספריית scripts/dev-scrub/ ואין scheduled TEST-data-refresh / anti-drift cron ב-pbsiem (קיים רק cron של rotate-secrets) — נתוני ה-staging מתיישנים, וזהו שורש הבעיה המתועד ל-gate-flake.
המציאות מקדימה את ה-runbook בכל הנוגע לתשתית ה-TEST/ephemeral, אך מפגרת אחרי ה-LOCKED PLAN בכל הנוגע ל-spine. סביבת ephemeral per-PR, full-system TEST deploy (mdr-engine + edr-puller + receiver + isolated per-PR backend stack), ומערך של 5 gates (e2e/dast/perf/visual/links) הם WIRED אמיתיים ו-green על PR-ים אמיתיים של pbsiem אל dev, מה שעולה על הטענה המיושנת של ה-runbook לפיה מדובר ב-'portal + DB branch only'. אבל ה-spine הנעול TEST->PROD שבור: prod-deploy.yml ו-prod-promote.yml מזיזים אך ורק את ה-Vercel portal alias (אומת: אפס שלבי engine/docker/ssh/hetzner; ל-PROD mode מבוסס-env של siem-deploy.yml אין caller), כך שה-engine מגיע ל-PROD רק דרך deploy ידני out-of-band ללא promotion מאומת מ-staging ל-prod. זהו ה-P0 שהפך שינוי של 40 דקות ל-11 שעות. מחמיר זאת: ה-data של MDR-PG/OpenSearch ב-TEST stack המשותף לעולם לא מתרענן (אומת: אין weekly anonymized dump), ולכן ה-gates flake על data ריק/מיושן; ה-staging אינו מתויג והשער האנושי היחיד הוא לחיצת portal-alias. פילר 4 רופף בשקט (lint:strict אינו נאכף ב-CI, Aikido נעדר למרות runbook שטוען 'back IN', CodeRabbit advisory, main מוגן רק על ידי ci/CI עם 0 approvals אך עם enforce_admins ON). מערך ה-battery של פילר 5 אמיתי אך דק (smoke spec אחד מתוך ~86, 3 EN visual baselines מתוך 24, ללא k6/a11y/mutation/cross-tenant/oasdiff/migration-safety). פילר 2 ומודלי ה-cost (EphemeralEnvCost, AICostLedger) ברובם לא נבנו. השורה התחתונה: ה-engine עובד עבור TEST; הבטחת ה-release-certainty של ה-plan ל-PROD עדיין אינה קיימת מכנית.
129 ממצאים על פני 14 תחומים, כל אחד מסווג ומגובה בראיות. חסר = מעולם לא נבנה. נבנה · לא מחובר = קיים ב-cicd-system אבל pbsiem לא קורא לו. נבנה אחרת = קיים אבל סוטה מהתוכנית. תואם = תואם לתוכנית (מקופל). לשונית משימות הופכת את אלה לרשימת משימות מסודרת.
סגרו את שני ה-P0 — נתיב ה-promote של המנוע ורענון נתוני ה-staging — ושדרוג Java/מנוע יעבור דרך staging ויצא ל-prod כ-promote יחיד, לא דיפלוי ידני של 11 שעות. 21 הנותרים אמיתיים, אבל הם חיזוק ולא חוסמים. חברו את מה שקיים, יישרו את מה שסטה, בנו את מה שחסר.
הניצחונות המהירים — ה-workflow כבר קיים ב-cicd-system; pbsiem פשוט לא קורא לו.
הקוד, ה-runbook והתוכנית לא מסכימים. תקנו את ההתנהגות, ואז יישרו את שלושתם.
חדש לגמרי. שני ה-P0 נמצאים כאן.