Skip to main content

Engineering substrate transparency aggregate substrate material not investment advice not advisory performance

Build in public

Engineering changelog

A date-ordered record of merged work across the Protocol Wealth engineering estate. Newest entries first. The same log shipped engineers read is published here — sanitized for client identifiers, with internal-repo pull-request references rendered as text rather than active links.

Substrate changelog — see /how-we-work for the underlying posture and /agents for the RIA agent substrate.

Source rendered at build time from shared/CHANGELOG.md. Last build: 2026-06-05.

Protocol Wealth — Cross-Repo CHANGELOG

Cross-repo log of merged PRs, filed issues, decisions, and shared artifacts for the active PW estate (pw-api, pw-os-v2, pw-portal-v2, pw-website, pw-infrastructure, pwos-core, nexus-core, pw-learnai, shared). Newest first. Scoped to anything cross-cutting; per-repo CHANGELOGs (where present) are authoritative for repo-internal detail. The status-doc CHANGELOG at status/CHANGELOG.md tracks PW-STATUS-AND-CAPABILITIES-*.md revisions only — separate ledger.

Style: No personal names; refer to roles (CTO/CISO, CCO, CIO, operator, operator). High-level summaries; PR tables retained but narrative kept compact. Aggregate PR counts over specific PR numbers; outcome statements over process detail; architectural summaries over file-level changes; quantified outcomes preferred. Formal sign-off provenance (Rule 204-2 / 17a-4 books-and-records) lives in shared/compliance/cco-approvals/ records where names + signatures are required for audit; CHANGELOG references those records by path, not by signatory name. The same no-names policy applies to shared/strategy/*, shared/compliance/COMPLIANCE-CHANGELOG.md, memory artifacts, and all public surfaces. Substantive-obligation records (cco-approvals, access-control-register, governance/, Form ADV) RETAIN names per Rule 17a-4 / Rule 206(4)-7 / SEC ADV substantive provenance requirements. See memory artifacts changelog-style-no-personal-names-high-level-summary + role-only-no-personal-names-policy-scope-extended-2026-05-19 for the complete scope inventory + two-tier provenance framework. Older entries (pre-2026-05-19) bulk role-substituted without full restructuring; full-rewrite future-only.


2026-05-30 (Onchain explorer — Solana lending/DeFi via Octav)

The advisor /onchain explorer now surfaces Solana lending / LP / vault positions via Octav (pw-api; no migration, no new infra). Operator decision after research: DeBank does NOT support Solana, and a keyless Kamino-only build was abandoned mid-flight; Octav — the complete cross-protocol source pw-onchain already integrated — is used on-demand with a bi-weekly (14-day) per-address cache, so a wallet hits the paid API at most once per two weeks (an ad-hoc lookup of a never-seen address still costs ~1 credit on first view). No infra step was needed — the OCTAV_API_KEY secret was already mounted in pw-api's Cloud Run (previously "no consumer code"). New Octav client ports pw-onchain's mapper (assetByProtocols → one position per protocol, USD value + protocol name + type) and wires into the Solana tracker as a best-effort add-on (empty when the key is unset, so key-less deploys + tests are unaffected). Double-count guard: Octav's wallet pseudo-protocol (token balances) and ALL staking-ish entries are dropped — native + LST staking is owned by the free native detection (shipped earlier the same day); Octav supplies only lending/borrowing/LP/farming/vault/perpetuals. A classifier bug was caught in test (it typed a solend_borrow protocol as lending because "solend" contains "lend" — fixed by checking borrow first). +8 unit tests. To verify post-deploy: the exact Octav response nesting + Solana chain id + borrow-value sign against a real Solana-lending wallet; the mapper refines if the structure is richer than pw-onchain's flat read.

2026-05-30 (Onchain explorer — Solana native-stake APY)

Native Solana stake positions now carry an estimated APY in the advisor /onchain explorer (pw-api; no migration, no new infra). Closes the operator's "I don't see an APY" gap: the liquid-staking-token path already showed Sanctum APYs, but native stake accounts (Marinade Native / Jupiter / Figment validators) showed none. Added a best-effort, 1h-cached, wallet-independent fetch of getInflationRate + getSupply + getVoteAccounts → a network base APY (inflation / staked_pct) and a per-validator commission map; each native-stake position's APY is then base_apy × (100 − commission)/100, using the position's representative validator commission (faithful to pw-onchain's calc that powers onchainstatement.com/staking). Fetched only when a wallet actually holds stake accounts, so stake-less lookups don't pay the heavy validator-set call; any failure degrades to a base-rate fallback rather than throwing. Validated against the operator's live wallet — Marinade Native / Jupiter / Figment now read ~5.2–5.6%, matching the legacy statement. Also completed the validator-name table (Figment etc. now render by name, not a raw vote pubkey). Reference-only port of pw-onchain _get_staking_data; verified by an adversarial workflow pass before build. Decided next: Solana lending positions — DeBank confirmed to NOT support Solana; Kamino's keyless public API is the verified primary candidate; Octav (paid, currently emergency-only) is the complete-coverage alternative — source/refresh-cadence is an operator decision pending.

2026-05-30 (Onchain explorer — Solana staking/LST detection)

The Solana tracker now detects staking and surfaces it as DeFi positions in the advisor /onchain explorer (pw-api + pw-os-v2; no migration, no new infra). Operator ask: proceed with detecting Solana staking/LST. Delivered two best-effort paths on SolanaTracker (a staking-fetch failure surfaces holdings without staking rather than throwing — staking is supplementary to the core holdings): (1) liquid-staking tokens — held LST mints (mSOL/jitoSOL/bSOL/JupSOL) are reclassified out of token balances into Staking positions (so the portfolio total isn't double-counted) carrying a live APY from the keyless Sanctum batch API; (2) native stake accounts — queried via getProgramAccounts on the Stake program by both authorized-withdrawer and authorized-staker offsets (Marinade Native sets the wallet as staker, not withdrawer, so one offset alone misses it), deduped, and grouped by platform family (Marinade Native / Jito collapse across validators) or per-validator. Sanctum API gotcha codified: the /v1/apy/latest endpoint zeroes at epoch boundaries (verified returning 0.0 for major LSTs on the build day), so the integration uses /v1/apy/inception (stable since-inception average) to avoid rendering a misleading 0% yield. The explorer UI surfaces the APY on staking-position rows (additive, graceful for positions without one). Native-stake APY is left for a follow-on (validator-commission-derived, needs the heavy getVoteAccounts call). Reference-only port of pw-onchain trackers/solana.py; +13 pw-api unit tests, all green. Closes the "Solana LST/staking" follow-on flagged when the multi-chain explorer shipped earlier the same day.

2026-05-30 (Onchain explorer — multi-chain + auto-detecting; Solana/BTC/XRP)

The advisor /onchain explorer became multi-chain with address auto-detection (pw-api #301 LIVE + pw-os-v2 #425; no migration, no new infra). Operator ask: add Solana (Jupiter) + auto-detect the chain + cover every chain we can support + drop the manual chains input + prompt the advisor when undetectable. Delivered: a detectAddressChain() classifier (EVM / Sui / Solana / Algorand / Bitcoin / XRP, ordering the prefix-bearing families ahead of the base58 fallthrough), three new native BaseTracker implementations — Solana (native SOL + SPL via the QuickNode Solana RPC getTokenAccountsByOwner/getBalance, priced by the keyless Jupiter Price v3), Bitcoin (keyless Blockstream + DeFiLlama price), XRP (XRPL account_info via the QuickNode XRP node + public-cluster fallback + DeFiLlama price) — and a summary-route refactor that drops the required chains= param: omit it and the address shape selects the chain, an unrecognized shape returns needs_chain_hint (HTTP 200) so the UI prompts the advisor with a manual chain picker, and chains= still works as an explicit override. The explorer UI drops the chains input, sends the address as-entered (no lowercasing — Solana/BTC/XRP are case-sensitive), and shows a detected-chain badge + override. Why Jupiter alone wasn't enough: Jupiter doesn't enumerate a wallet's holdings — that's a Solana RPC call — but pw-api already had a full QuickNode Solana node (provisioned for KYW), so holdings work with zero new infra; Jupiter supplies the USD prices. Reuses the existing QuickNode Solana/XRP endpoints; Jupiter / Blockstream / DeFiLlama are keyless. Reference-only patterns from nexus-core (app/chain.py, data/onchain/jupiter.py) + pw-onchain (trackers/{solana,bitcoin,xrp}.py), reimplemented natively in pw-api per their charter. +26 tests; all green. Follow-on: Jupiter Token API (keyed) for full SPL symbols, Solana LST/staking, a dedicated Solana RPC endpoint.

2026-05-30 (Phase-3 pw-onchain Slice 4 PR-5b/5c — reader cutover BUILT, HELD for review)

The Fly-proxy holdings-reader cutover is built and open as two HELD PRs (pw-api #300 + pw-os-v2 #424; no auto-merge; no migration). This swaps the live client-portfolio holdings reader, so it awaits operator review + dogfood before merge. Design is deliberately safe — opt-in native + Fly fallback: the native reader aggregates a live tracker fan-out across a client's tracked EVM/Flare wallets (keyed by wallets.client_id) into the existing section shape, but returns null (opt-out) when the client has no native wallets, so the legacy Fly group proxy still serves every un-migrated client — no existing client's holdings change until wallets are seeded. Both read paths cut over (the advisor REST /portfolio/client-view + the tRPC portfolio.getClientView, now sequential since the native read shares the request DB client). A new /portfolio/onchain-history endpoint reads the entity_type='client' rollup snapshots (PR-5a) for the client-card balance-over-time chart. The client card (5c) flips "Source: pw-onchain" → "pw-api", drops the clients.metadata.onchain_group_name empty-state instruction (native keys off wallets.client_id), adds the history chart + timeframe picker, and fixes a React key collision in the holdings wallet list. The operator chose a live tracker call for the current total + the snapshot rollups for history. All green locally (typecheck/lint/build; +7 unit/BFF tests; a pw-api integration spec runs in CI). Merge order: #300 then #424; Fly stays as fallback until native is proven. This completes the Slice-4 build — only the operator's review/merge of the cutover remains.

2026-05-30 (Phase-3 pw-onchain Slice 4 PR-5a — writer client/group rollups)

The daily snapshot writer now emits entity_type='client'/'group' rollup rows (pw-api; additive, no migration). The foundation for the held reader cutover (PR-5b/5c): after each tenant's wallet rows commit for the day, the writer recomputes that tenant's client + group daily_snapshots rollups by summing today's wallet rows joined to wallets for client_id / group_name (daily_pnl vs the prior-day rollup). It runs in its own transaction so a rollup failure never rolls back the wallet snapshots, is idempotent, and recomputes from all of today's rows so it converges across cursor batches. The daily_snapshots CHECK already admits client/group entity_type, so no migration. Nothing reads these rows yet (the Fly proxy still serves holdings), so this landed safely ahead of the cutover. +2 integration cases (green in CI's Migration dry-run). The remaining reader cutover (native loadOnchainSection off wallets.client_id with Fly fallback + the client-card history chart + set-group-from-UI + the "Source" label flip) stays HELD for operator sign-off, with the operator's choice locked: a live tracker call for the current total + the snapshot rollups for history.

2026-05-30 (Phase-3 pw-onchain Slice 4 PR-4 — lookup_wallet_history chat tool)

Address-keyed onchain history chat tool shipped (pw-os-v2; the last decoupled Slice-4 piece). lookup_wallet_history — advisor-gated, read-only — wraps the pw-api performance route (daily_snapshots) to return a daily total-USD series with the net change over a window (default 30 days) for a single EVM address; empty for untracked addresses (graceful hint to lookup_wallet_portfolio). Reconciled against the existing address-keyed tools: the originally-planned get_wallet_balance was dropped as redundant (lookup_wallet_portfolio already returns an address's live balance), so lookup_wallet_history is the genuinely-new time-series surface. Pure formatter unit-tested (5 cases); registers cleanly into the tool registry. With this, Slice-4 PRs 1–4 are live; only the PR-5 Fly-proxy holdings-reader cutover remains, held for operator sign-off (approach locked: the daily writer also emits entity_type='client'/'group' rollup rows — no migration — then the native reader keys off wallets.client_id).

2026-05-30 (Phase-3 pw-onchain Slice 4 PRs 2+3 — duplicate-address lookup + Manage-Wallets UX)

Manage-Wallets refinements + a compliance string fix shipped (pw-api PR-2 + pw-os-v2 PR-3; no migration). Folds operator feedback Part A item 4 into the inline wallet manager on the advisor client portfolio Onchain card. pw-api (PR-2): new read-only GET /v1/internal/onchain/wallets/lookup matches an address across both the wallets table (tracked + soft-deleted) and the client_wallets KYW-link table — matched on the address (case-insensitive for 0x-hex, verbatim for base58/bech32), since the two tables label chains differently — returning { already_tracked, wallet_matches[], client_wallet_matches[] }; audited as a duplicate_lookup view. pw-os-v2 (PR-3): the add-a-wallet form now offers a single labeled "EVM — Ethereum mainnet + all DeBank chains" choice (sub-chain field dropped) plus solana/bitcoin/xrp/flare; a select-existing-or-"add new" group combobox seeded from a new groups BFF proxy; a per-wallet Refresh button (+ auto-fire on add) that pulls a live balance via the explorer summary route (EVM/Flare only); and duplicate-address detection on add that warns — without blocking, since joint custody is legitimate — naming where the address is already tracked. Compliance: the client crypto-toggle confirm string no longer names the decommissioned Chainalysis vendor or legacy "pw-onchain reads"; it now reads "Scorechain sanctions + KYW screening … native onchain holdings reads." Two new advisor BFF proxies (groups + lookup; pw-api owns the audit) with 3 unit tests; a dedicated pw-api integration spec (8 cases, green in CI). The UI degrades gracefully if the lookup endpoint isn't deployed yet (advisory pre-check + empty-group fallback). Slice-4 remainder: lookup_wallet_history chat tool (PR-4); Fly-proxy holdings-reader cutover (PR-5, held for operator sign-off).

2026-05-30 (Phase-3 pw-onchain Slice 4 PR-1 — /onchain address explorer)

/onchain advisor address explorer shipped (pw-os-v2; decoupled, no migration). First of four Slice-4 PRs. Paste any EVM address → live multi-chain balance + per-chain / top-token / DeFi-position breakdown (pw-api onchain portfolio summary route) plus a balance-over-time chart (performance route → daily_snapshots). The previously-disabled /overview "Onchain explorer" hub card is now live; the route is advisor-gated (mirrors /market-data) so cost-bearing DeBank lookups stay off the employee surface. Two read-only BFF proxies (GET /api/advisor/onchain/explorer/{summary,history}, SYSTEM_TENANT_ID + session actor_email injected; pw-api owns the audit) added to the existing onchain BFF router — zero new mount. New reusable inline-SVG BalanceHistoryChart (no charting dependency — to be reused by the Slice-5 client portfolio card). Daily history populates only for tracked wallets with accrued snapshots; untracked addresses still show live holdings, no series. 7 new BFF unit tests; full 1533-test API suite + lint + typecheck green; the explorer ships as its own lazy bundle chunk (main bundle untouched). Slice-4 remainder: Manage-Wallets UX refinements + decommissioned-Chainalysis-confirm-string fix (PR-2/3); lookup_wallet_history chat tool (PR-4 — reconciled, since lookup_wallet_portfolio already covers address-keyed live balance); Fly-proxy holdings-reader cutover (PR-5, HELD for operator sign-off; approach locked = the daily writer also emits entity_type='client'/'group' rollup rows, which the schema already permits with no migration).

2026-05-29 (Friday — Phase-3 pw-onchain Slice 2: daily-balance writer cron)

Phase-3 pw-onchain port — Slice 2 daily-balance writer SHIPPED (pw-api half; pw-api #296). The producer that turns the Slice-3 wallet rows into snapshot history — unblocks the Slice 3.5 self-heal corrector (it now has daily_snapshots to walk) + the Slice 4 history reads. src/lib/onchain-snapshot-writer.ts (runSnapshotWriterPass) clones the proven snapshot-self-healing.ts tick: per-tenant createRequestClient, cursor-resumable over wallet id, duration-budgeted, returns 200 on every reachable outcome. Selects wallets due by next_snapshot_at (chain_type IN ('evm','flare') — the tracker-covered chains; solana/btc/xrp await trackers), calls getEVMTracker/getFlareTracker honoring the throw-on-exhaustion contract (a thrown fetch is a hard fail — no $0 snapshot, next_snapshot_at NOT bumped so the next fire retries), writes per-token wallet_snapshots (is_recent demoted) + an upserted daily_snapshots row (entity_type='wallet', entity_id=<lowercase address>, total = holdings + positions when include_defi, daily_pnl vs the prior daily row), bumps last/next_snapshot_at. Emits onchain.snapshot.written per wallet + onchain.daily_rollup.completed per tick (both pre-declared; the crypto address is PII-redacted in after_state). New cron endpoint POST /v1/internal/cron/onchain/snapshot/run-next (OIDC-only, cursor + batch_size query overrides). Env ONCHAIN_SNAPSHOT_{BATCH_SIZE=50,MAX_DURATION_MS=9min} are code-side .default()no Cloud Run env mount; only the Cloud Scheduler is new. No migration. 10-case integration suite green against real Postgres in CI (caught + fixed two test bugs pre-merge: a cross-test contamination from the all-due-wallets pass, and an assertion that expected the un-redacted address in the audit after_state). Cloud Scheduler pwllc-prod-onchain-snapshot-tick (pw-infrastructure #222, fires 02:00 UTC — before the 03:00/03:30/04:00 pw-api tick cluster so its fresh daily_snapshots feed the 04:00 self-heal corrector) was APPLIED + smoke-tested GREEN (2026-05-30 03:24Z): operator merged pw-infrastructure #222 → GitHub Actions WIF auto-applied; manual gcloud scheduler jobs run returned HTTP 200 tick_completed (processed:0 — wallets table empty — in 148ms, no silent-401), job ENABLED, next fire 02:00 UTC (smoke-test gate per feedback_scheduler_tick_smoke_test satisfied). Slice 2 LIVE end-to-end; daily_snapshots accrue once wallets are tracked via the Slice-3 surface. Next: Slice 4 (advisor balance + history lookup UI + chat tools).

2026-05-29 (Friday — Phase-3 pw-onchain Slice 3: native onchain wallet CRUD + groups + client assignment)

Phase-3 pw-onchain port — Slice 3 pw-api substrate SHIPPED (pw-api #295). Native wallet management on the wallets table (empty until now), the substrate the Slice 2 daily-balance writer needs before it has rows to snapshot. Replaces the legacy Fly pw-onchain wallet CRUD + the clients.metadata->>'onchain_group_name' linkage hack. New OIDC-gated internal routes: POST /v1/internal/onchain/wallets (create/track — idempotent on (tenant_id, chain_type, address); reactivates a soft-deleted row, 409 on an active dup; sets next_snapshot_at=now() so Slice 2 picks it up; lowercases 0x EVM/flare addresses; validates client_id in-tenant; re-track preserves prior client/group via COALESCE), PATCH /onchain/wallets/:id (update/assign/clear — null clears a field; re-enabling tracking refreshes next_snapshot_at), DELETE /onchain/wallets/:id (reversible soft-delete + untrack), GET /onchain/groups (wallet-group read axis — group_name + wallet/tracking/client counts + client_ids), plus a client_id filter on the existing GET /onchain/wallets. Audit verbs onchain.wallet.{tracked,updated,untracked} (.updated is new; all TypeScript-only — audit_log.action is plain TEXT with no CHECK). No migration — the schema was complete at migration 0019 and the group model is the shipped denormalized wallets.group_name string (matches daily_snapshots.entity_type='group'), not a new FK table. All writes touch only pw-api's own wallets table (no upstream-vendor mutation), so they ride the internal REST surface alongside the KYW client_wallets link/unlink precedent. 23-case integration suite green against real Postgres in CI (Migration dry-run job); adversarially reviewed pre-merge (the re-track field-clobber footgun was caught + fixed). The Fly-proxy reader cutover (loadOnchainSection → native) is deferred to after Slice 2 populates daily_snapshots. Advisor UI (slice-3 part 2) SHIPPED (pw-os-v2 #420): an OIDC BFF proxy (advisor-onchain-wallets.ts) + an inline "Manage wallets" section in the Onchain card on ClientPortfolioPage (OnchainWalletManager — add/track + edit group/label + soft-delete; client_id-keyed, independent of the Fly-proxy holdings breakdown). UI placement = expand-the-card per operator decision. Slice 3 COMPLETE. Re-sequenced remainder: Slice 2 (daily-balance writer cron) → Slice 4 (advisor balance + history lookup UI + chat tools).

2026-05-29 (Friday — /demo state surfaces deployed; Chainalysis sanctions vendor decommissioned; FMP/Chainalysis internal-doc sweep; Chainalysis fully decommissioned + KYW (Scorechain Risk/QuickNode) LIVE; multi-chain onchain holdings layer LIVE on pw-api; pwos.app/systems cleanup; pw-onchain Phase-3 port started; Tatum onboarded)

/demo state surfaces shipped to pwos.app + pwportal.app. Static state-surface pages served at /demo on both apps (pw-os-v2 #414, pw-portal-v2 #83) with a follow-up scoped-CSP fix (pw-os-v2 #415, pw-portal-v2 #84) so the inline-styled + Google-Fonts pages render under each app's strict global CSP without relaxing any other route. platform-state.md wired as manually-curated chat grounding on both surfaces.

Chainalysis OFAC sanctions source decommissioned (pw-api #287). Confirmed LIVE (not dead) at removal — secret provisioned + mounted, real per-screen OFAC calls OR-combined into the verdict. Sanctions screening is now Scorechain Free Sanctions only (OFAC + OFSI + MOFA + NBCTF). chainalysis_result column + historical rows RETAINED (Rule 204-2 / 17a-4); new rows stop writing it. Terraform secret + env mount DESTROYED (pw-infrastructure #214/#215); dormant 'chainalysis' type-union literals REMOVED (pw-api #288, pw-portal-v2 #85); CCO governance sign-off RECORDED (shared #447). CTO-authorized; CCO-ratified. Client-facing Privacy Policy + subprocessors edits drafted for CCO review (strategy/2026-05-29-cco-draft-chainalysis-removal-octav-refine.md) — NOT published.

KYW (Scorechain Risk via QuickNode) LIVE. Wallet-risk scoring (KYW) is now returning real scores in production — a SEPARATE Scorechain product from Scorechain Free Sanctions (the two are independent). Secrets + mounts wired (pw-infrastructure #216/#217); param-shape fix landed (pw-api #289: sc_getAddressAnalysis takes hash-only). 7 chains live: eth / base / btc / sol / xrp / avax / arbitrum (Arbitrum KYW mount pw-infrastructure #219). Verified returning real scores.

Multi-chain onchain HOLDINGS layer LIVE on pw-api (rev 00286). New tracker layer at src/lib/trackers/: DeBank/EVM (existing) + FlareTracker (pw-api #292, QuickNode native FLR) + TatumTracker (pw-api #293, native HyperEVM / Algorand / Sui via the Tatum gateway, x-api-key). Env mounts (pw-infrastructure #221: QUICKNODE_FLARE_RPC_ENDPOINT + TATUM_API_KEY) + secrets (#220). GET /v1/internal/onchain/portfolio/summary now returns these chains (address-keyed, native-only).

pwos.app/systems cleanup (pw-os-v2 #418). Retired pw-strategies / pw-signals / pw-insights / pw-onchain from the systems surface; onchain is now served by pw-api. nexus-core / nexusmcp.site presented as the live open educational MCP.

Phase-3 pw-onchain PORT started. Slice 0 (systems) + slice 1 (holdings) DONE. nexus-core is REFERENCE-ONLY — build in pw-api, do NOT consume it (its charter is anonymous / educational / no-client-data). Re-sequenced remaining work: slice 3 (wallet rows + groups + client→wallet assignment; wallets table is EMPTY) → slice 2 (daily-balance writer cron) → slice 4 (advisor balance+history lookup UI + chat). Heavy follow-on (tables ported, no writers): statements, cost-basis, tax, LP, staking/yield, eth-risk. Legacy source: /mnt/c/Users/nick/Desktop/PROJECTS/pw/pw-onchain. Plan: memory project_pw_onchain_port_2026_05_29.

Vendor ops. Tatum onboarded (68-chain gateway, Starter plan) as the native-balance source for HyperEVM / Algorand / Sui. DeBank API key rotated.

FMP/Chainalysis internal-doc sweep. Corrected stale current-tense FMP references (PWOS.md, alphavantage.md, emf-canonical.md) and added decommission/staleness banners (architecture/api/chainalysis.md, PW-CACHING-LAYER-ARCHITECTURE.md). FMP was removed 2026-05-14 (ToS commercial-use); MBOUM + SEC EDGAR are the fundamentals sources. Octav confirmed manual emergency-restore-only (out of daily pipeline; no build — YAGNI).

2026-05-28 (Thursday — OSS Strategy v1.2 published; CFP Board fact sheet drafted for Friday Zaniello call; Claude Opus 4.8 frontier swap; PWOS Coordinator Module design + P1 build)

Open Source Strategy v1.2 published to the firm's public surface via pw-website #122. New top-level page at https://protocolwealthllc.com/opensource-strategy carries the canonical hubs / licensing / AI-governance / public-vs-private statement adapted from shared/docs/compliance/opensource-policy.md v1.2. §1.4 HITL Tier 2 canonical phrase preserved verbatim from the post-reconciliation source; §7.4 synthetic-data discipline carried over as the firm-wide commitment. Sanitization scrubbed internal PR numbers, internal authority notes, and shared/strategy/ cross-references; project-management sections (current-state snapshot, roadmap status, implementation phases, risks register, metrics, decision-log) dropped from the published version while the strategic surface (frame, external projects to absorb, license compatibility matrix, attribution discipline) retained. Footer adds a "Open Source Strategy" link sibling to the existing GitHub "Open Source" link; /opensource hero gains a cross-link to the new page. CTO/CISO-autonomous publication of an already-canonical posture; no Marketing Rule §206(4)-1 client-recommendation surface added.

CFP Board fact sheet drafted at shared/strategy/2026-05-29-cfp-board-zaniello-fact-sheet.md (this PR) for the Friday 2026-05-29 3pm Zaniello call. ~1,040 body words across six sections: opening verdict; what PW has built (concrete artifacts — @protocolwealthos/[email protected] + [email protected], 19-package npm scope, Reg-S-P §248.30 inspection-ready, ZDR-enrolled Anthropic workspace); why fiduciary-aligned (HITL Tier 2 + Marketing Rule + Reg-S-P + synthetic-data); what CFP Board could examine (schema + published strategy URL + reference architectures + 2-person intern adopter feedback loop); what PW is asking (directional alignment check + schema-evolution PR invitation, not endorsement); closing dialogue framing. Marked DRAFT — CCO REVIEW PENDING per dispatch; four operator-input slots at the bottom flag (a) specific question(s) to put to CFP Board, (b) CCO review window, (c) pre-read attachments, (d) tone calibration. Phrase A (HITL Tier 2) cited verbatim from opensource-policy.md §1.4; Phrase B (ZDR API-surface) cited verbatim from wisp-ai-posture.md §1 bullet 4.

Claude Opus 4.8 frontier model swap — Anthropic shipped Claude Opus 4.8 on 2026-05-28 as the new flagship; CLAUDE_MODEL_FRONTIER bumped from claude-opus-4-7 to claude-opus-4-8 on pw-os Cloud Run via pw-infrastructure #213, with sibling cost-table row landed via pw-os-v2 #407. Only Opus advanced; Sonnet 4.6 (WORKHORSE) and Haiku 4.5 (LIGHTWEIGHT) unchanged. Zero breaking API changes from 4.7 — identical feature set + 1M context + 128k output + adaptive-thinking-only + $5/$25 per MTok pricing. Anthropic-documented gains for PWOS workloads: better tool triggering (fewer skipped tool calls on the 64-chat-tool surface), better long-horizon agentic coding, better compaction recovery, more reliable per-effort calibration. PWOS coordinator tier (which pins modeOverride='max' → CLAUDE_MODEL_FRONTIER) inherits 4.8 on next Cloud Run revision rollout. Three new 4.8 features (mid-conversation system messages, refusal stop_details.category, fast-mode research preview) deferred to separate follow-on PRs; a dedicated Claude Opus 4.8 research-session prompt is staged at shared/strategy/2026-05-28-claude-opus-4-8-research-session-prompt.md for a focused investigation of 4.8 best practices across PWOS chat and local Claude CLI.

PWOS Coordinator Module — design + P1 MVP end-to-end in one session. Same-day delivery from design proposal through P1 build of the partner-tier orchestrator surface. Design landed via shared #421shared/strategy/PWOS-COORDINATOR-MODULE-DESIGN.md 7-section DRAFT proposing P1-P4 phasing; P1 cleared to build as REVIEWABLE-PR (system-prompt edits capability-bearing); P4 (officer-approval substrate) explicitly BLOCKED on CCO design review per §4.P4. Three corrections vs prior session-memory baked into the doc: (a) session_mode enum is advisor/troubleshoot/develop per migration 0038, not produce; (b) pre-existing PR #406 was OPEN not merged; (c) audit-archive lock effectiveTime is 2026-04-17T23:13:26Z, not the 2026-05-02 in memory reference_audit_archive_lock. P1 MVP shipped via pw-os-v2 #409 (CCO+CTO ratified, REVIEWABLE) — POST /api/protected/chat/conversations widened for purpose field with partner-title gate; DEFAULT_TIER_AWARENESS_SNIPPET appended to default system prompt + COORDINATOR_TIER_CONFIRMATION prepended to coordinator system prompt (closes both directions of the self-audit gap surfaced earlier same day where the in-chat model reported "there's no Coordinator in this tool surface"); partner-only "🌐 Start Coordinator Chat" button in sidebar topSlot; read-only Coordinator badge in chat header. PATCH/promote button shipped via pw-os-v2 #411 — PATCH /conversations/:id purpose handling + interactive promote/demote button replacing the read-only badge; updateConversationPurpose() helper added; audit row carries via='promote'|'demote' to distinguish from POST path's via='create_conversation' (CCO examiner pivot is symmetric across all three vectors via action='conversation.purpose.set' AND resource_id=<conv_id>). Sibling attachment expansion via pw-os-v2 #410 — CSV / TSV / JSON added to SUPPORTED_MIMES via the existing text-inline path; new TEXT_INLINE_MIMES export. Sidebar polish via pw-os-v2 #412 — cyan "coordinator" badge in conversation list. Supersession: pw-os-v2 #406 closed as superseded by #411 (couldn't update-branch after #409 landed; conflicts on chat.ts; rebuilt fresh under #411 with same intent + P1 framing). Living-state: shared #422 captured the wave in PWOS-STATE.md RECENT. Carry-forward to next session: P2 (decisions/action-items tracker + Daily Update content surface) needs design memo before build; P3 (ADR review surface) similar; P4 BLOCKED on Adam design review per §4.P4 of the design doc.

Langfuse model registry cleanup (no PR — direct API writes against self-hosted v2.95.11 at langfuse.protocolwealthllc.com). Added claude-opus-4-8 model def at correct $5/$25 per MTok pricing matching PWOS-canonical apps/api/src/lib/claude-cost.ts. Cleaned duplicate claude-opus-4-7 entries (deleted 1 null-tokenizer dup, recreated 1 at correct $5/$25 schema matching the 4.8 def field-for-field — same unit=TOKENS + tokenizerId=claude + tokenizerConfig={} + clean modelName). Pre-cleanup state had 4.7 mis-priced at the OLD pre-PR-7.5b $15/$75 rate (the 3× error PWOS's own claude-cost.ts corrected on 2026-05-11; the Langfuse registry had drifted out of sync). Final state verified: 4 PW custom Claude defs (4.7 / 4.8 / Sonnet 4.6 / Haiku 4.5) all schema-identical + pricing aligned with Anthropic published rates + matchPatterns cover the live PWOS env strings (claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5-20251001).

Reg S-P / Reg XP June 3, 2026 deadline countdown: 6 days remaining.


2026-05-27 (Tuesday close — P0.K1 KEYSTONE ARC FULLY CLOSED; integration suite caught latent prod bug pre-deploy)

P0.K1 keystone arc closed end-to-end today. Three sequential closes:

  1. P0.K1.next CLOSED 10:29Z — operator re-smoke against the deployed-fixed pw-api-00273-t2m revision returned clean tick_started + tick_completed + HTTP 200 + accounts_processed=0 (expected; no Altruist tenant onboarded yet). The 2026-05-26 EOD first-tick RLS-tenant-context throw confirmed fixed.

  2. P0.K1.test CLOSED 15:31Z via pw-api #284 — 12-test integration suite shipped at test/integration/positions-ingest-cron.spec.ts: RLS-context regression centerpiece (FIX-HOLDS + BUG-REPRODUCES under FORCE-RLS-engaged pwapi_app role — the pre-deploy guard for #283-class bugs), per-account isolation e2e, content-hash dedup + sentinel-supersedes + immutability trigger, 3-strike retry trail, adapter-auth-failed batch continuation, zero-account graceful exit (the 10:29Z re-smoke shape pinned permanently into CI), cursor pagination over real Postgres. Initial PR landed via Option B (5/12 active + 7 skipped pending production fix).

  3. PRODUCTION BUG SURFACED + FIXED via pw-api #285 (merged 16:21Z) — the suite caught a latent TEXT=UUID type-mismatch at altruist-positions.ts:323 that would have thrown operator does not exist: text = uuid on the first real Altruist account onboarding. Migration 0038 widened audit_log.resource_id UUID→TEXT but the subquery still compared resource_id = ps.id (UUID column). Root cause confirmed via throwaway 3-point probe (commits 1fda761 add + f5f80b7 revert, preserved in history for governance auditability). Fix: one-character ps.id::text cast + defensive explicit tenant_id = current_setting('app.tenant_id', true)::uuid filter in the sweeper SELECT at cron-cursor.ts (defense-in-depth on top of FORCE RLS; protects against role-posture regressions, produces #283-style loud-failure on missing context instead of silent cross-tenant rows). 12/12 orchestration tests green. 1727/1727 unit baseline preserved. Lint + typecheck clean.

Companion strategy doc landed via shared #397 at shared/strategy/2026-05-27-tool-replacement-parity-map.md — sequences the 11 P1-DEPENDENTS against the operator's stated RightCapital/Monarch/Wealthbox cancellation goal (Monarch first/nearest parity, RightCapital second/every-calculator-net-new, Wealthbox last/~12-month parallel-write); Sections 2 + 4 fill-in templates for the operator+partners ~20-minute conversation.

Substantive outcome: pre-deploy integration test (#284) caught a pre-existing production defect (#285) that no manual review or unit-test layer had surfaced — the exact value proposition the dispatch invoked when ordering keystone integration tests as the next CLI build post-#283. The 12-test CI gate now permanently catches future regressions in this code path.

Keystone arc PR audit trail: substrate (pw-api #280, 2026-05-26), cron-tick RLS-context fix (pw-api #283, 2026-05-27 03:13Z), integration centerpiece (pw-api #284, 15:31Z), production cast + sweeper hardening (pw-api #285, 16:21Z).

OSS surface went live today. @protocolwealthos/[email protected] + @protocolwealthos/[email protected] published to npm under nickrygiel maintainer; names locked. pwos-core monorepo now publishes 19 packages total (17 pre-existing + 2 new). disclosure-card is the flagship adoptable-standard artifact for AI-system disclosure — Zod runtime validation + JSON Schema + CI gate for verbatim required-string presence. shared carries the HITL fail-closed gate primitive + SHA-256 provenance hash-chain. Both earned focused sibling-package surfaces (disclosure-card was previously a subpath within shared; the split lets each carry its own README, examples, and adoption surface independently). Friday-ready as the standards artifact for the CFP Board / Zaniello conversation. Workflow gotcha + workaround: the Release workflow's NPM_API_KEY doesn't have write permission to create NEW packages in the @protocolwealthos scope; worked around via manual user-publish from local under maintainer nickrygiel. Non-blocking follow-up: token-fix before the next intended version bump. CTO-autonomous publishing event (no Adam pre-clearance — no client-facing content); CCO informed per cross-firm transparency norm; logged to shared/compliance/COMPLIANCE-CHANGELOG.md.

Reg S-P / Reg XP June 3, 2026 deadline countdown: 6 days remaining. Next: BlockSkunk Phase-0 kickoff call later today (signed engagement, 50% paid).


2026-05-26 (Monday OVERNIGHT close — P0.K1 first-tick RLS bug + fix shipped + DEPLOYED; Schwab dropped; iteration close)

P0.K1 first-tick prod bug + fix shipped + DEPLOYED tonight. First prod smoke-test against the operator-applied pwllc-prod-positions-altruist-tick Cloud Scheduler (pw-infrastructure #205, applied earlier evening) threw "invalid input syntax for type uuid: \"\"" at 6ms — surfaced via the smoke-test gate per memory feedback_scheduler_tick_smoke_test.

Root cause: the sweeper SELECT against the FORCE-RLS-protected custodian_accounts ran via raw opts.pool.connect() with no app.tenant_id session var set. The RLS USING policy current_setting('app.tenant_id', true)::uuid returns '' when missing_ok=true (NOT NULL), and ''::uuid throws. My altruist-positions-tick.ts diverged from the canonical per-tenant-context pattern at snapshot-self-healing.ts:485 which uses createRequestClient(tenantId) to set context BEFORE any RLS-protected query.

Fix: pw-api #283 — per-tenant sweep loop via createRequestClient(tenantId) (default [SYSTEM_TENANT_ID]; multi-tenant generalization via opts.tenantIds); cursor-normalize 3-layer defense-in-depth (cron.ts handler + altruist-positions-tick.ts entry + cron-cursor.ts function entry; new normalizeCursor() pure helper handles null/undefined/'' → null); zero-tenant graceful exit (batch_completed audit still emits using tenantIds[0] so ops dashboard query stays meaningful on idle ticks); explicit $2::uuid cast in SQL; 7 new unit tests in test/unit/positions-ingest-cron-cursor.spec.ts. 1727/1727 unit tests pass; lint + typecheck clean.

Deploy status: PR #283 MERGED at 03:05:43Z; Deploy pw-api completed success at 03:13:21Z; current live revision pw-api-00273-t2m (past the buggy pw-api-00272-gkk). Operator re-smoke against the deployed-fixed revision PENDING tomorrow (NOT re-run in this close-out — re-smoking 00272 would reproduce the bug; must wait for deploy-confirm + re-smoke against 00273-t2m).

Schwab path REFRAMED (operator decision 2026-05-26 EOD): Altruist Open API surfaces Schwab-custodied accounts directly (PW receives Schwab-held assets via the existing source='altruist' adapter); no separate Schwab file-batch adapter needed for visibility. P0.K2 DEMOTED to DEFERRED-LOW in ROADMAP; the keystone substrate already covers Schwab via Altruist ingest. Only open Schwab question is lot-level cost-basis fidelity (synthetic-single-lot fallback handles aggregate-only sources until/unless a direct Schwab feed is wanted). Consequence: tomorrow's first CLI build = P0.K1.test integration tests (incl. RLS-context regression — THE test that would have caught pw-api #283 pre-deploy); THEN operator picks first unblocked P1.D* dependent, NOT a second adapter.

Diagnostic verification report (read-only; no remediation)

Surface Method Result
Migration 0058 applied in prod gcloud run jobs executions list --job=pw-api-migrate 5 successful executions today (latest 03:12:13Z from #283 deploy); substrate physically exists. Direct DB query blocked by private-IP per dispatch direction.
12 keystone audit verbs canonical-shape static regex check against pw-api/src/lib/audit-actions.ts All 12 PASS the 3-segment parseAction regex (fix-forward A from shared #395 fully landed).
pw-api ERROR logs (last 6h) gcloud logging read severity>=ERROR --freshness=6h 3 first-tick throws (02:47/02:49/02:51 UTC) — ALL the known RLS bug from operator smoke-test. Nothing new/unexplained.
pw-os ERROR logs (last 6h) same Zero errors.
Scheduler pwllc-prod-positions-altruist-tick gcloud scheduler jobs describe ENABLED, schedule 0 12 * * *, time_zone Etc/UTC, URI + SA correct, attemptDeadline 720s.
Scheduler pw-api-snapshot-self-healing-nightly (didn't get disturbed) same ENABLED, schedule 0 4 * * *.
Scheduler pwos-market-scan-run-next (didn't get disturbed) same ENABLED, schedule */2 9-10 * * *.
P1.25 EXEMPT confirm grep audit_log_action_check across migrations No CHECK migration on audit_log.action ever shipped; verbs are TypeScript-only via PW_ACTIONS + isCanonicalAction() per Clarification B. Keystone surfaces EXEMPT confirmed.

Drift + hygiene captured

  • P1.P15 NEW (tracked follow-up; NOT remediated): pw-api Cloud Run terraform-vs-gcloud drift surfaced by tonight's terraform plan -target=google_cloud_scheduler_job.pw_api_positions_altruist_tick — pw-api google_cloud_run_v2_service.pw_api showed client = "gcloud" -> null + scaling block change. Likely from .github/workflows/deploy.yml running gcloud run services update pw-api --image <sha> per pw-api/CLAUDE.md §Deploy pipeline. Same class as memory feedback_terraform_env_var_drift; reconcile in a focused follow-up PR.
  • Stale-branch sweep: ~100+ [gone] branches deleted across 6 repos (open-PR safe; active-worktree-protected). Branches in shared/.claude/worktrees/ checkouts skipped (will clean themselves up when those agent sessions next run).
  • All 6 local repos re-anchored to origin/main.

Estate artifacts

Repo PR Title
pw-api #283 fix(positions-ingest): P0.K1 first-tick RLS-tenant-context throw + cursor-normalize defense-in-depth — MERGED + DEPLOYED
shared (this PR) docs(close-down): P0.K1 fix-deployed verification + Schwab dropped + P1.P15 infra-drift + memory + NEXT-PROMPT

Memory updates

  • NEW feedback_cron_sweeper_force_rls_tenant_context.md — durable lesson: cron/sweeper querying a FORCE-RLS table MUST use createRequestClient(tenantId) to set app.tenant_id before the query; raw pool.connect() throws ''::uuid on RLS policy eval. Canonical pattern: snapshot-self-healing.ts:485.
  • UPDATED project_p0k1_keystone_live_2026_05_26.md — RLS-fix-pending-verification status; Schwab dropped; dependent-not-adapter framing for tomorrow.

Doc-health audit (canonical docs current/stale)

Doc Last touched Current/stale
shared/ROADMAP.md 2026-05-27 overnight CURRENT (this close-out)
shared/CHANGELOG.md 2026-05-27 overnight CURRENT (this entry)
shared/strategy/CURRENT-STATE.md 2026-05-27 overnight CURRENT (this close-out re-anchor)
shared/architecture/PWOS.md v1.7 2026-05-26 EOD CURRENT (§4.1.1 keystone inventory accurate; clarification on Schwab-via-Altruist folded in this close-out where adapter section warranted)
shared/architecture/decisions/ADR-positions-cost-basis-model.md Revision 3 2026-05-26 EOD ACCEPTED, current — Decision 6 acquired_at CIO FYI accurate
shared/architecture/decisions/ADR-custodian-data-ingest-orchestration.md Revision 2 2026-05-26 EOD ACCEPTED, current — Decision 6 verb-name table reflects SHIPPED canonical 3-segment forms (fix-forward A from #395 landed)
shared/runbooks/positions-ingest.md 2026-05-26 EOD CURRENT — §1 smoke-test gate is the load-bearing reading for operator re-smoke tomorrow
shared/NEXT-PROMPT.md 2026-05-27 overnight CURRENT (this rewrite — deploy-confirm-gated re-smoke first)
shared/architecture/BLUEPRINT.md 2026-05-08 STALE (tracked P1.P14; not refreshed tonight per dispatch direction)
pw-infrastructure/CLAUDE.md 2026-05-12 STALE (tracked P1.P14; not refreshed tonight per dispatch direction)
pw-api/CLAUDE.md 2026-05-22 STALE (no recent state-paragraph refresh; not flagged in scope tonight)
pw-os-v2/CLAUDE.md 2026-05-22 STALE (no recent state-paragraph refresh; not flagged in scope tonight)
pw-portal-v2/CLAUDE.md 2026-05-22 STALE (no recent state-paragraph refresh; not flagged in scope tonight)
~/.claude/projects/-home-nick-projects-pw/memory/MEMORY.md 2026-05-27 overnight CURRENT (this index update)

No doc still references governance.review_item.* (verified earlier via grep in PR #395 close-down). No doc references the inaccurate "0046 CHECK-widening" framing on the governance verb (PR #395 fixed all instances).


2026-05-26 (Monday EOD — P0.K1 keystone implementation LIVE; iteration-close)

The P0.K1 keystone branch shipped end-to-end this iteration — both gating ADRs ACCEPTED (schema + ingest orchestration), substrate migration LIVE in pwllc-prod, first-consumer Altruist ingest shipped, cross-repo PII canonical-add triplet maintained byte-equal across pw-api / pw-os-v2 / pw-portal-v2, canonical read contract typed-helper module landed, operator runbook published. 6 PRs across 3 repos total this iteration; substrate is READY to run, PENDING operator Cloud Scheduler terraform apply + smoke-test gate before the cron starts firing daily at 12:00 UTC.

6-PR keystone-build tally

PR Repo Title Scope
#280 pw-api Keystone substrate — migration 0058 + 12 audit verbs + PII_TAGS for 4 new tables 4 tables (custodian_accounts + positions_snapshots + position_lots + position_dispositions scaffold) + 2 UNION VIEWs (tax_lots_unified + tax_dispositions_unified) + immutability trigger on positions_snapshots + 23 PII unit tests + 6 audit-verb unit tests; LIVE in pwllc-prod
#397 pw-os-v2 PII byte-equal port — 4 new tables Cross-repo canonical-add triplet per pii-tags-cross-repo-sync-required-on-canonical-add memory
#82 pw-portal-v2 PII byte-equal port — 4 new tables Same as above
#281 pw-api Altruist positions ingest — first consumer of substrate 4 lib modules (content-hash.ts + cron-cursor.ts + retry-trail.ts + altruist-positions.ts + altruist-positions-tick.ts) + POST /v1/internal/cron/positions/altruist/run-next endpoint + 2 new altruist-client fetchers (fetchPositionsForAccount + fetchCostBasisForAccount) + 14 unit tests; all 7 operator-confirmed defaults from Open Q1–Q7 RESOLVED baked in
#282 pw-api positions-read.ts — canonical read contract for 11 P1-DEPENDENTS 4 typed helpers (readPositionsForAccount + readOpenLotsForAccount + readAccountView + readClientPortfolio) per schema ADR Decision 5; computed-at-read totals (PnL never denormalized)
#394 shared runbooks/positions-ingest.md — operator runbook Altruist-only at v1 per Open Q7; smoke-test gate + failure-class triage flowchart + retry-budget reset + Decision 6 dashboard queries

Migration 0058 verified read-only-safe against onchain tables (2026-05-26 EOD verification report): peer-table + UNION VIEW pattern confirmed; zero write statements against cost_basis_lots / cost_basis_dispositions / daily_snapshots / any other onchain table; immutability trigger attached to positions_snapshots only; position_lots is genuine peer (own CREATE TABLE; FK to custodian_accounts(id), NOT wallets). No fork-instead-of-peer risk.

Verb-name canonicalization (caught + fixed inline; fix-forward to ADR in this close-down)

The ingest ADR Decision 6 table originally listed three verbs in 2-segment shape that would have failed the canonical 3-segment regex enforced by parseAction at pw-api/src/lib/audit-actions.ts:728 + the 'all action values conform' assertion at pw-api/test/unit/audit-actions.spec.ts:12. Implementation correction landed the canonical 3-segment forms per the existing <table>.<singular>.<verb> precedent (clients.client.created / households.household.created):

ADR DRAFT text SHIPPED canonical 3-segment form
custodian_accounts.synced custodian_accounts.account.synced
position_lots.acquired positions.lot.acquired (groups under positions.* namespace alongside .snapshot.* + .ingest.*)
position_lots.superseded positions.lot.superseded
positions.ingest.retry.success / .retry.failure (dotted) positions.ingest.retry_success / .retry_failure (underscored; 3-segment)

The ADR Decision 6 table is updated in this close-down PR to reflect the SHIPPED names; pw-api/src/lib/audit-actions.ts PW_ACTIONS registry is canonical for all consumers. Durable lesson codified to memory: ADR specs that enumerate audit-verb names should be verb-shape-validated against the canonical regex BEFORE promotion to ACCEPTED.

P0.K1 status

  • Substrate: LIVE in pwllc-prod via pw-api #280; migration 0058 dry-run + applied + verified.
  • Ingest code: shipped via pw-api #281; READY to run but not yet running on cadence.
  • PENDING operator action P0.K1.next (NEW ROADMAP row): Cloud Scheduler pwllc-prod-positions-altruist-tick terraform apply + 1-hour smoke-test gate per feedback_scheduler_tick_smoke_test memory. Runbook §1 enumerates the procedure. This is the gate between substrate-built and ingest-running.
  • All 11 P1-DEPENDENTS unblocked at substrate level: P1.D1 portfolio mgmt + P1.D3 client portal positions + P1.D4 IPS Maker + P1.D5 Investment Presentation Maker + P1.D6 cash-flow planning + P1.D7 performance reporting + P1.D8 rebalancing + P1.D9 cross-custodian tax reporting + P1.D10 EMF/PWAF CIO surface + P1.D11 chat-tool portfolio writes + P1.D2 Component 6 onboarding dashboard. All read via pw-api/src/lib/positions-read.ts typed helpers per schema ADR Decision 5.
  • Next adapter on proven model: P0.K2 Schwab (file-based per 2026-05-17 gemini synthesis). Operator picks when to dispatch.

Deferred follow-ups captured as new ROADMAP rows

  • P0.K1.next — Cloud Scheduler apply + smoke-test (operator action; the gate)
  • P0.K1.test — keystone integration-test PR (cron-tick smoke + per-account-isolation e2e vs Altruist mock; Postgres-harness-bound)
  • P0.K1.disposition — disposition consumer (sale-vs-transfer-vs-correction per deferred Decision 7)
  • P1.P13 — PII egress canary HITL build (better detector + tokenizer + advisor disambiguation + per-span attestation; needs own ADR + Adam-CCO sign-off; supersedes earlier simpler "canary-block UX message" framing)
  • P1.P14 — BLUEPRINT.md + pw-infrastructure/CLAUDE.md staleness refresh (tracked per 2026-05-23 doc-health audit)

Estate artifacts (this close-down PR)

Repo PR Title
shared (this PR) docs(close-down): P0.K1 keystone iteration close — ROADMAP + ingest-ADR verb-name fix-forward + CURRENT-STATE + PWOS v1.7 + NEXT-PROMPT + runbook cross-refs

Files touched

  • shared/ROADMAP.md — NEW §2.5 Recently SHIPPED; P0.K1 row marked DONE; 4 new follow-up rows (P0.K1.next + P0.K1.test + P0.K1.disposition); 2 new P1.P-platform rows (P1.P13 PII canary HITL + P1.P14 BLUEPRINT staleness tracker); F1 marked SHIPPED.
  • shared/architecture/decisions/ADR-custodian-data-ingest-orchestration.md — Decision 6 table verb names canonicalized to SHIPPED forms (3 verbs + retry pair); 4 in-body verb references updated; naming-canonicalization callout added below the table.
  • shared/CHANGELOG.md — this EOD entry.
  • shared/strategy/CURRENT-STATE.md — verdict anchor re-anchored to 2026-05-26 EOD.
  • shared/architecture/PWOS.md — bumped v1.6 → v1.7 with keystone substrate inventory addition.
  • shared/NEXT-PROMPT.md — cold-start opener for 5/27 leads with operator Cloud Scheduler apply + smoke-test.

Coverage closed + opened

  • P0.K1 keystone branch SHIPPED end-to-end — substrate LIVE; ingest READY; runbook published.
  • 11 P1-DEPENDENTS UNBLOCKED at substrate level (consumer side is positions-read.ts; per-dependent UI/BFF work lands in separate P1.D* dispatches).
  • Cross-repo PII canonical-add triplet discipline EXERCISED + GREEN across pw-api → pw-os-v2 → pw-portal-v2 within 30-60s window.
  • Operator next action = Cloud Scheduler apply + smoke-test gate (P0.K1.next in ROADMAP).

2026-05-26 (Monday evening — ADR-custodian-data-ingest-orchestration PROMOTED DRAFT → ACCEPTED; 7 operator open questions resolved + 2 clarifications baked in + ROADMAP F2 fix-forward + schema ADR minor edits)

ADR-custodian-data-ingest-orchestration.md promoted DRAFT → ACCEPTED. All 7 operator open questions resolved with operator-accepted defaults; 2 substantive clarifications applied before promotion. P0.K1 keystone implementation PR now unblocked against both ACCEPTED ADRs (schema + ingest).

Seven open questions RESOLVED with operator-accepted defaults:

# Question Resolution
1 Altruist cron cadence 12:00 UTC daily (conservative; 7am ET)
2 Per-tick account bound 50 accounts (3x over-provisioned at current ~15-account scale; load-bearing at 100+ clients)
3 Retry budget per account 3 strikes (3 days at daily cadence)
4 First-sync mode Manual operator-triggered at v1 (visibility during first-adapter rollout; auto-trigger considered for v2)
5 Cloud Scheduler region us-central1 (matches pw-api Cloud Run region)
6 Per-tenant advisory-lock Yes (409 on concurrent invocation)
7 Runbook scope at v1 Altruist-only (extends per-adapter as each lands)

Clarification A — disposition-detection scope (NEW Decision 7 added): ingest captures STATE only at v1. The prior position_lots.disposed audit verb (in Revision 1 Decision 6 verb taxonomy) is REMOVED — it belonged to the disposition consumer, not ingest. Sale-vs-transfer-vs-correction disambiguation is deferred to a separate later consumer (Altruist transactions ingest is the canonical disposition signal source via its explicit transaction_type='SELL' field per altruist.md §4.5). Rationale: a quantity-delta between snapshots can mean SALE / TRANSFER-OUT / CUSTODIAN CORRECTION — three vastly different tax treatments; auto-classification as a disposition risks wrong realized-gain reporting that lands on a client's tax return. The cost of false-positive disposition >> cost of deferred disposition surfacing. Decision 7 flags consequent open consequence for P1.D7 performance reporting (TWR/IRR cash-flow precision degrades on sales until disposition consumer lands). Verb count corrected 12 → 11.

Clarification B — mechanism precision (CHECK widening vs TypeScript-only) applied throughout the ingest ADR + the schema ADR + ROADMAP F2:

Mechanism When applied What it requires
CHECK-widening migration Adding a new custodian_accounts.source value (per schema ADR Decision 2) or a new review_items.kind enum value .sql migration following the migration-0046 pattern (DROP then ADD CONSTRAINT)
TypeScript-only registration Adding a new audit_log.action verb (per ingest ADR Decision 6 + any future per-adapter or per-consumer verbs) .ts file edit in pw-api/src/lib/audit-actions.ts (PW_ACTIONS registry + isCanonicalAction() allowlist). audit_log.action is plain TEXT with NO CHECK to widen.

The two mechanisms are now consistently distinguished throughout the docs; the prior conflations are fixed.

CIO FYI (NOT a gate; downstream reporting consequence): Altruist's aggregate open_date returned by /v2/accounts/{id}/cost-basis reflects the EARLIEST underlying lot's acquisition date (verified empirically against altruist.md §4.4 schema example). Synthetic-single-lots therefore carry acquired_at values that may pre-date the client's actual buy-in by years. Tax treatment: conservative + long-term-gain-favorable. Reporting + UX consequence: client positions on pwos.app/clients/$id + pwportal.app will display acquisition dates that look surprising for clients who joined PW recently ("why does this position show a 2019 date?"). P1.D1 + P1.D3 implementation PRs should render a tooltip / footnote on synthetic-aggregate positions. Jason-CIO already signed the related schema ADR Decision 4 + Open Question 2 aggregate calls earlier today (Revision 2 of schema ADR); this is the surfaced REPORTING effect of that limitation, not a new gate. Flagged in both ADRs (ingest ADR Decision 5 Altruist overlay + schema ADR Decision 6 §synthetic-single-lot).

ROADMAP F2 fix-forward (single-line edit; operator-flagged in the dispatch): the F2 entry previously read "migration (signing_tier column + audit verb add)" — implying audit-verb addition was a migration concern. Corrected to split: migration handles signing_tier column on review_items + partner_signoff admission to review_items kind CHECK enum (THIS is migration-0046 widening pattern; kind IS a CHECK enum); audit verb registration is TypeScript-only via PW_ACTIONS + isCanonicalAction() (audit_log.action is plain TEXT). Note added: canonical review-items audit verbs follow the review.item.* namespace pattern (per ADR-review-items-primitive.md line 123), NOT governance.review_item.* — preemptively codified though no instance of the wrong name exists in current shared/ docs (verified via grep).

Schema ADR ADR-positions-cost-basis-model.md minor edits (Revision 3; status stays ACCEPTED — NO re-promotion):

  • Audit-verb registration mechanism corrected in Implementation-PR scope (was "via migration-0046 CHECK-widening pattern" → corrected to TypeScript-only per Clarification B).
  • position_lots.disposed verb removed from Implementation-PR scope verb list (per ingest ADR Decision 7; verb belongs to disposition consumer, not ingest).
  • CIO FYI callout added in Decision 6 §"synthetic single-lot pattern": Altruist aggregate open_date is the EARLIEST underlying lot's date — downstream REPORTING consequence flagged to Jason-CIO (already signed related calls; this is the surfaced reporting effect).
  • New Revision 3 entry added at bottom.

Estate artifacts

Repo PR Title
shared (this PR) docs(adr): ADR-custodian-data-ingest-orchestration DRAFT → ACCEPTED; 7 operator answers + 2 clarifications + ROADMAP F2 fix-forward + schema ADR Revision 3

Files touched (4)

  • EDITED shared/architecture/decisions/ADR-custodian-data-ingest-orchestration.md — Revision 2 (DRAFT → ACCEPTED). All substantive edits: frontmatter status flip + gates ✅; Status section rewrite; NEW Decision 7 added (disposition-detection scope STATE-ONLY at v1); Decision 6 verb taxonomy revised (position_lots.disposed removed; Clarification B precision note prepended); Adapter overlays prefaced with Clarification B mechanism-precision note; Decision 5 Altruist overlay gains CIO FYI callout for open_date reporting consequence; Open Questions section converted from "Open" to "RESOLVED at ACCEPTED promotion"; Implementation-PR scope verb-count corrected 12 → 11 with Clarification B precision; Revision 2 entry added.
  • EDITED shared/architecture/decisions/ADR-positions-cost-basis-model.md — Revision 3 (status stays ACCEPTED; minor edits only). Audit-verb registration mechanism corrected; position_lots.disposed removed from scope list; CIO FYI callout added in Decision 6 §synthetic-single-lot; Revision 3 entry added.
  • EDITED shared/ROADMAP.md — F2 entry edited: signing_tier column / partner_signoff admission split from audit-verb registration; canonical review.item.* verb namespace pattern noted.
  • EDITED CHANGELOG.md — this entry.

Coverage closed + opened

  • P0.K1 keystone implementation PR: now UNBLOCKED against both ACCEPTED ADRs (schema + ingest). The implementation PR follows verbatim — schema is binding; operational contract is binding; 6 + 1 = 7 substrate decisions cover all design calls; no inline-decision re-debate at implementation time.
  • P0.K2 Schwab + P0.K3 IBKR + manual: per-adapter overlay framework in place (Decision 5 + new Clarification B mechanism precision); each adapter's dispatch ships overlay-only PR scope.
  • Disposition-detection consumer (Altruist transactions ingest + Schwab transactions file-ingest + cross-custodian transfer-detection): NEW dispatch surface opened — separately scoped from ingest per Decision 7. P1.D7 + P1.D9 dependents may need to coordinate with this consumer's timing.
  • ROADMAP F2 conflation closed; canonical verb-namespace pattern preemptively codified.
  • Schema ADR audit-verb registration mechanism: corrected to match the canonical TypeScript-only pattern; the prior CHECK-widening claim was a vestige of an earlier dispatch shape (per feedback_verify_dispatch_schema_claims_before_authoring_migrations memory — the lesson applies).

2026-05-26 (Monday evening — ADR-custodian-data-ingest-orchestration DRAFT authored as F1-companion; gates P0.K1 implementation PR jointly with the schema ADR)

NEW shared/architecture/decisions/ADR-custodian-data-ingest-orchestration.md DRAFT authored as the F1-companion to the schema ADR (ADR-positions-cost-basis-model.md ACCEPTED earlier today). The two ADRs jointly govern the P0.K1 implementation PR: schema ADR locks the data shape, this ADR locks the operational contract — trigger/cadence/idempotency/partial-failure/backfill — across all keystone adapters (Altruist, Schwab, IBKR, manual, plus the existing onchain reference). Without this ADR the implementation PR would have decided the ingest contract implicitly inline, and P0.K2 Schwab + P0.K3 IBKR would have inherited whatever choices the first PR made.

Recommendation accepted (own ADR vs Decision 7 in keystone ADR): OWN ADR. Cross-cutting concern across N adapters (the schema is ONE decision; ingest is per-adapter overlay on a shared primitive). Lifecycle decoupled — schema rarely re-promoted; ingest evolves continuously as adapters land. Precedent: ADR-webhook-receiver-primitive.md (own ADR despite tight webhook-handling code coupling). F6 / P1.P10 KYC + RT crons can cross-reference the trigger primitive shape without inheriting keystone-substrate-specific concerns.

Six decisions surfaced explicitly:

  • Decision 1 — Trigger + cadence: Cloud Scheduler /v1/internal/cron/<adapter>/run-next cursor-resumable primary (adopts the existing snapshot-self-healing primitive at pw-api/src/routes/internal.ts:281-284); webhook-augmented where source supports (Altruist NO; Quiltt YES via existing path); on-demand fallback for operator + first-sync. Per-adapter cadence: Altruist daily 12:00 UTC; Schwab daily 11:00 UTC (post-SFTP-drop); IBKR TBD per P0.K3; manual N/A; onchain (existing) stays 04:00 UTC. Reconciles with F6 — KYC/RT crons may adopt the same primitive shape but define their own per-surface work.
  • Decision 2 — Snapshot idempotency: content-hash dedup (SHA-256 over canonical-JSON of quantity + market_value + unit_price + currency_code + source_position_id; excludes provenance fields) + sentinel-row supersedes for corrections + natural-grain per (custodian_account_id, instrument-key, as_of_date). Application-layer dedup discriminates "identical re-fetch" (skip silently, no audit) from "corrected re-fetch" (INSERT with supersedes_id + emit superseded audit row). Same pattern adapted for position_lots (synthetic-aggregate vs real-lot upgrade handled via supersedes chains).
  • Decision 3 — Partial-vs-fail-closed posture: PARTIAL COMMIT per-account-atomic. If Altruist returns 10 accounts and 2 fail, the 8 commit + the 2 emit positions.ingest.account_failed audit rows with classified error + retry counter. Per-account isolation; 3-strike retry budget before WARN escalation; sentinel-row retry trail per ADR-gcs-worm-audit-mirror Pattern #2. 17a-4 substantiation: partial-commit IS the records-correct posture (failed records have audit-row evidence; successful records are immutably retained); all-or-nothing fail-closed would be WORSE (would abort 8 valid records on 1 transient failure).
  • Decision 4 — First-sync / backfill: current-day-only at v1; historical backfill DEFERRED to P0.K3. Altruist Open API surfaces "most recent positions" only (per altruist.md §4.3); Historical Data Feed (SFTP) is a separate provisioning flow. Schema trivially supports backfill (no constraint on as_of_date direction); backfill is additive when P0.K3 lands.
  • Decision 5 — Adapter overlays: per-source operational notes for Altruist (P0.K1 — sequential per-account fetch, 1 req/sec safety, OAuth refresh per existing pattern), Schwab (P0.K2 — file-batch SFTP polling, per-file cursor), IBKR (P0.K3 — TBD), manual (P0.K3 — operator-action only), onchain (existing — unchanged), Quiltt (existing webhook path — unchanged).
  • Decision 6 — Audit + observability: 12 new canonical 3-segment audit verbs registered in pw-api/src/lib/audit-actions.ts (custodian_accounts.synced, positions.snapshot.captured, positions.snapshot.superseded, position_lots.acquired/superseded/disposed, positions.ingest.account_failed/token_failed/list_failed/constraint_violation/batch_completed, positions.ingest.retry.success/failure). Skipped-duplicate events emit structured-log INFO only (no audit row; silent operation). Ops dashboard queries enumerated for "did every advisor sync today" + "how many accounts have pending failures" + per-tick latency p95.

Seven open questions flagged for operator confirmation before ACCEPTED promotion (all CTO-operational; NO CCO/CIO gates): cron cadence for Altruist (default 12:00 UTC); per-tick account bound (default 50); retry budget (default 3 strikes); first-sync mode (default manual operator-triggered vs auto on OAuth-callback); Cloud Scheduler region (default us-central1); per-tenant advisory-lock for concurrency (default yes); runbook scope at v1 (default Altruist-only).

Implementation-PR scope enumerated: new Cloud Scheduler job pwllc-prod-positions-altruist-tick (operator-direct terraform); new endpoint POST /v1/internal/cron/positions/altruist/run-next; new lib modules under pw-api/src/lib/positions-ingest/ (altruist-positions.ts + content-hash.ts + retry-trail.ts + cron-cursor.ts shared primitive); 12 new canonical audit verbs; new shared/runbooks/positions-ingest.md operator runbook; cron-tick smoke + per-account isolation + token-refresh-failed + content-hash dedup + sentinel-retry-trail tests.

Keystone schema ADR updated with cross-reference (status stays ACCEPTED — minor edit only):

  • Frontmatter references list adds the new ingest ADR
  • Implementation-PR scope section notes the two ADRs jointly govern the implementation PR

Estate artifacts

Repo PR Title
shared (this PR) docs(adr): ADR-custodian-data-ingest-orchestration DRAFT — F1-companion; gates P0.K1 implementation PR jointly with schema ADR

Files touched

  • NEW shared/architecture/decisions/ADR-custodian-data-ingest-orchestration.md — DRAFT (~600 lines). 6 decisions; 7 open questions; implementation-PR scope; adapter taxonomy; per-source overlays; alternatives considered; references.
  • EDITED shared/architecture/decisions/ADR-positions-cost-basis-model.md — minor: frontmatter references adds the new ingest ADR + Implementation-PR scope section notes joint-governance. Status stays ACCEPTED (no re-promotion).
  • EDITED CHANGELOG.md — this entry.

Coverage opened

  • P0.K1 implementation PR — when the operator confirms the 7 open questions + flips this ADR to ACCEPTED, the implementation PR is unblocked against the joint schema + operational contract.
  • P0.K2 Schwab + P0.K3 IBKR + manual adapters — inherit the shared contract; per-adapter overlays are thin; no per-adapter contract re-debate.
  • F6 / P1.P10 Cloud Scheduler tick wire-up — KYC + RT crons can cross-reference the trigger primitive without inheriting keystone-substrate concerns. F6 stays independently scoped.

2026-05-26 (Monday evening — ADR-positions-cost-basis-model PROMOTED DRAFT → ACCEPTED per Jason-CIO sign-off)

ADR-positions-cost-basis-model.md promoted DRAFT → ACCEPTED per Jason-CIO sign-off on the three CIO-gating calls. The P0.K1 keystone implementation PR is now unblocked against the ACCEPTED schema. Substrate stays schema-binding without code-binding — implementation PR follows next iteration.

Three CIO answers baked in as substantive revisions:

  • Decision 4 reframed — FIFO-hardcoded across both tables → specific-lot identification (TradFi) + FIFO (onchain, preserved verbatim) + automatic average-cost (synthetic-single-lot). CIO directive: "ideally you can select which lots you are selling, but average works as well." The TradFi position_lots disposition path is specific-lot-selectable (caller names the lot at write time); the onchain cost_basis_dispositions path stays FIFO verbatim (shipped + Form 8949 tested; not touched); the synthetic-single-lot for aggregate-only sources (Decision 6) automatically yields average-cost (only one lot to consume from). The cross-custodian tax_lots_unified VIEW spans two methods; year-end Form 8949 default is per-source method (IRS-correct posture for crypto vs traditional brokerage as separate account types). Read-time method derivation from the consumed lot's acquisition_source allows future override without schema change.

  • Open Q2 RESOLVED, no change — CIO directive: "trust Altruist prices, all highly liquid tier-1 assets." The synthetic-single-lot pattern stands as drafted; Decision 6 commentary expanded to note the synthetic lot IS the average-cost case automatically (no special-case code path). Alternative B (separate aggregate field) rejection reinforced.

  • Open Q4 RESOLVED as trust-but-verify two-layer — positions_snapshots gains OPTIONAL independent_unit_price_usd + independent_price_source + independent_priced_at columns. OPERATIONAL: custodian's market_value_usd IS the canonical statement value (no independent price gates the statement). AUDIT: independent fields are a backup verification capability for Adam-CCO's standing spot-check requirement; NOT operational gates. Same posture extends to vault NAVs later. Implementation PR ships the carrying schema + placeholder helper; first concrete spot-check consumer (operator-issued vs scheduled-canary) is a follow-up design call out of scope for P0.K1.

Remaining open questions (Q1, Q3, Q5, Q6, Q7) carried forward as CTO implementation calls or deferrals — NOT CIO gates; do NOT block ACCEPTED status nor the P0.K1 implementation PR.

Documentation impact (the ADR-internal substantive edits):

  • Frontmatter: status: DRAFT → ACCEPTED; gates annotated with ✅ for CTO operator-authority + Jason-CIO sign-off
  • Status section rewritten to enumerate the three CIO sign-offs in detail + remaining open questions
  • Decision 3 schema: 3 new OPTIONAL columns on positions_snapshots
  • Decision 4 substantially revised (peer tables + per-source method framing replaces FIFO-hardcoded framing); index names dropped _fifo suffix (specific-lot UI uses same access pattern); position_dispositions commentary updated (disposition method derived at read time from lot's acquisition_source; no schema column added)
  • Decision 6 commentary expanded to highlight automatic average-cost behavior of synthetic-single-lot
  • Open Questions reorganized: Q1, Q2, Q4 marked RESOLVED with CIO citation; Q3, Q5, Q6, Q7 carried forward
  • Implementation-PR scope: replaces FIFO test with specific-lot + synthetic-single-lot degenerate average-cost + onchain FIFO pinning + independent unit-price spot-check tests; adds placeholder helper for independent-price recording
  • Alternative B rationale strengthened with CIO confirmation citation
  • Revision 2 entry added at bottom

Estate artifacts

Repo PR Title
shared (this PR) docs(adr): ADR-positions-cost-basis-model DRAFT → ACCEPTED per Jason-CIO sign-off; specific-lot + onchain-FIFO + trust-but-verify pricing

Files touched

  • EDITED shared/architecture/decisions/ADR-positions-cost-basis-model.md — Revision 2 (DRAFT → ACCEPTED). All substantive revisions above; ~140 lines net added (60 lines net edits + Revision 2 entry).
  • EDITED CHANGELOG.md — this entry.

Coverage closed + opened

  • All three CIO-gating calls satisfied: Decision 4 (lot accounting method); Open Q2 (synthetic single-lot fallback); Open Q4 (pricing snapshot policy).
  • P0.K1 implementation PR unblocked against the ACCEPTED schema; can begin next iteration.
  • Onchain Form 8949 paths preserved verbatim — no destructive migration of shipped cost_basis_lots + cost_basis_dispositions; the new substrate is additive (peer tables) per Decision 4.
  • Trust-but-verify pattern codified as a substrate primitive — same pattern will extend to vault NAVs when those land.

2026-05-26 (Monday evening — ROADMAP F1 LANDED: ADR-positions-cost-basis-model.md DRAFT authored)

ROADMAP F1 (the gating ship for the P0.K1 keystone branch) LANDED as DRAFT ADR. New shared/architecture/decisions/ADR-positions-cost-basis-model.md is the binding architecture decision the keystone implementation PR follows next iteration. Schema-binding without code-binding: NO migration, NO implementation in this PR.

Six decisions surfaced explicitly (each with rejected alternatives):

  • Decision 1custodian_accounts finalized column shape (canonical PW account identity table; multi-tenant + FORCE RLS + 7-year retention + pii_tags JSONB; updates permitted — current-state row, append-only discipline lives on positions_snapshots not here).
  • Decision 2 — Custodian-agnostic provenance via source TEXT CHECK enum (altruist/quiltt/schwab_dailyfile/ibkr/onchain_wallet/manual) + source_account_id TEXT + UNIQUE(tenant_id, source, source_account_id). This is what makes Schwab a SECOND ADAPTER not a second schema (P0.K2 commitment).
  • Decision 3positions_snapshots finalized column shape + as-of-date semantics: append-only point-in-time time-series; immutability trigger mirrors account_balances (migration 0055); sentinel-row supersedes per ADR-gcs-worm-audit-mirror.md Pattern #2 for corrections; partial index excluding superseded rows covers the dominant "latest non-superseded per account per instrument as-of date" read.
  • Decision 4 — Lot accounting unification: peer tables (position_lots peer to existing onchain cost_basis_lots from migration 0020, NOT a replacement) with read-side UNION via new tax_lots_unified VIEW. Preserves shipped onchain Form 8949 query paths verbatim; cross-custodian tax reporting (P1.D9) reads via the VIEW with consistent FIFO semantics. Folded-into-one-table alternative explicitly rejected for shipping-cost vs operational-aesthetics tradeoff.
  • Decision 5 — Canonical read contract for the 11 dependents (P1.D1–P1.D11): stored facts (quantity/market_value/unit_price/cost_basis_total) vs computed-at-read (unrealized PnL, account totals, cross-custodian roll-ups, concentration breakdown, TWR/IRR, realized PnL). Substrate stays simple; aggregates derived from immutable facts can't drift.
  • Decision 6 — Altruist-ingest as first consumer + synthetic-single-lot pattern for lot-less sources. Altruist Open API exposes aggregate-level cost-basis only (per altruist.md §9 "Tax lot limitation"); ingest creates ONE position_lots row per (account, instrument) with acquisition_source='aggregate'. Upgradable via sentinel-row supersedes when Altruist Realtime API or SFTP Data Feed surfaces real lots. Preserves unified FIFO + Form 8949 read path across sources.

Seven open questions flagged for operator + CIO sign-off before promotion to ACCEPTED: (1) lot unification scheme (CIO-valuation-methodology call); (2) synthetic single-lot vs separate aggregate field (CIO-tax-reporting call); (3) as-of-date correction discipline (CTO-recommended sentinel always); (4) pricing snapshot policy (CIO call on whether to capture independent market-data vendor unit_price alongside custodian's); (5) cash flow events for TradFi (widening migration 0021's cash_flow_events.source CHECK vs separate table); (6) manual position entry timing (CTO-recommended defer to P0.K3); (7) multi-currency (CTO-recommended defer).

Implementation-PR scope enumerated for the iteration that follows: migration (3 new tables + position_dispositions sidecar + 2 VIEWs + 4 new canonical audit verbs via 0046 CHECK-widening pattern); lib extensions (pii-tags.ts cross-repo byte-equal port to pw-os-v2 + pw-portal-v2; new positions-read.ts typed helpers; audit-actions.ts registration); ingest worker (altruist-sync.ts extension with new fetchPositionsForAccount + fetchCostBasisForAccount fetchers; synthetic-lot creation per Decision 6 mapping table); test coverage (FIFO consumption + ON CONFLICT idempotency + sentinel-supersedes correction + VIEW UNION shape pin).

Substrate posture: EXEMPT from P1.25 hard-gate per ROADMAP Decision 1 (reuses existing classified audit_log path). No CCO disclosure perimeter on substrate (perimeters land per-dependent at P1.D* gates). Adam-CCO not a gate on this ADR; Jason-CIO sign-off required on Decision 4 + Open Question 2 + Open Question 4 before promotion to ACCEPTED.

Estate artifacts

Repo PR Title
shared (this PR) docs(adr): ADR-positions-cost-basis-model DRAFT — F1 / P0.K1 keystone gating ship

Files touched

  • NEW shared/architecture/decisions/ADR-positions-cost-basis-model.md — DRAFT (~600 lines). 6 decisions; 7 open questions; implementation-PR scope; rejected alternatives; references all 7 cited migrations + altruist.md + 2 sibling ADRs.
  • EDITED CHANGELOG.md — this entry.

Coverage opened

  • P0.K1 implementation PR — unblocked (schema is binding); follows verbatim next iteration.
  • All 11 P1-DEPENDENTS — read contract codified; substrate they consume will exist after the implementation PR ships.
  • P0.K2 Schwab adapter + P0.K3 IBKR + manual — second/third-source adapters extend the source CHECK enum via migration-0046 widening pattern; no new schema. Substrate proven by Altruist first-consumer ingest.
  • Cross-custodian Form 8949 export (P1.D9)tax_dispositions_unified VIEW shape committed.

2026-05-26 (Monday evening — reconciled roadmap PROMOTED to canonical shared/ROADMAP.md with 4 operator decisions baked in)

Reconciled all-inclusive roadmap promoted to canonical shared/ROADMAP.md with 4 operator-authority decisions applied as edits BEFORE promotion. The prior shared/strategy/2026-05-26-reconciled-roadmap-of-record.md DRAFT (shared #388) is now a redirect notice pointing to the canonical register. The /admin/approvals partner-signoff scope doc (shared #387) updated in the same PR to match Decision 2.

Decision 1 — P1.25 hard-gate clarification (Compliance Hub Phase D + classification automation): applies ONLY to surfaces requiring NEW classification (the substrate add-on itself); the existing classified audit_log path is EXEMPT. /admin/approvals partner.signoff.recorded, keystone positions_snapshots audit, and P1.A1 library/socials/content-drafts writeAuditLog additions all reuse the existing classified path — they do NOT block on P1.25.

Decision 2 — /admin/approvals signing-model reframe: passkey/OAuth signature_hash click + WORM audit_log + Google Workspace/Vault backup is the CANONICAL PRIMARY ceremony for ALL partner approvals (CCO, CISO, CIO). Anvil is reframed as an OPTIONAL formal-PDF-export wrapper for the narrow set of filed regulatory records where a countersigned PDF artifact is wanted (examiner expectation); same auth assurance, presentation-only. DocuSign is RETIRED — removed from the flow. ESIGN/UETA bootstrap C4.6 is no longer prerequisite for surface go-live; deferred until first PDF-export opt-in. CONSEQUENCE: the surface works DAY ONE on passkey + WORM + Vault with NO Anvil dependency.

Decision 3 — Planning-tier disclosure-language seed row: NEW PL1-disclosure seed row added to §3.7 covering all P2.PL calculator outputs (P2.PL1 Roth, P2.PL2 RMD, P2.PL3 Social-Security, P2.PL4 Medicare/IRMAA, P2.PL5 tax-ref dataset). Single batched CCO touch (NOT per-calculator); ships ahead of all four calculator surfaces.

Decision 4 — P2.PL5 versioned tax-ref dataset reordered AHEAD of P2.PL1: the versioned tax-ref foundation that all four planning calculators block on is reordered to ship FIRST. Can-start-now (no upstream dependency); unblocks P2.PL1-P2.PL4 sequentially.

Estate artifacts

Repo PR Title
shared (this PR) docs(roadmap): promote reconciled roadmap → ROADMAP.md with 4 operator decisions; reframe #387 signing model

Files touched

  • NEW shared/ROADMAP.md — canonical register written from scratch (~600 lines): §1 how to read; §2 ship this week (F1-F7); §3 dependency-ordered queue (P0-DEADLINE → P0-KEYSTONE → P0-GOVERNANCE → P1-PLATFORM → P1-DEPENDENTS → P2-PRODUCT → P3-EXPLORATION); §4 gated parked list; §5 reconciliation appendix; §6 cross-references; §7 what this doesn't decide. P1.25 carries EXEMPT clause (Decision 1); P0.G1-G4 reframed (Decision 2); §3.6a planning tier reordered (Decision 4); §3.7 includes NEW PL1-disclosure row (Decision 3).
  • REWRITTEN shared/strategy/2026-05-26-reconciled-roadmap-of-record.md (#388) — replaced 442-line DRAFT with short redirection notice pointing to ROADMAP.md (drift foot-gun avoided).
  • EDITED shared/strategy/2026-05-26-admin-approvals-partner-signoff-surface-scope.md (#387) — header status block, §1 intent, §2 constraint #2, §5 (canonical passkey + optional Anvil PDF wrapper), §6 (bootstrap downgraded to optional Phase 0c later-enabler), §10 (Q1 downgraded to "which records want PDF export — default NONE"), §11 (build size downgraded; passkey-only ships faster).
  • EDITED shared/strategy/CURRENT-STATE.md — anchor refresh citing promotion + Decision 2 signing-model reframe.

Coverage closed + opened

  • Roadmap dispersion (4 source scope/discovery docs + prior ROADMAP.md → one canonical register): CLOSED.
  • /admin/approvals signing-model ambiguity (dual PATH 1/PATH 2 per-kind framing): RESOLVED in favor of passkey-canonical + Anvil-optional wrapper (Decision 2); surface ships day-one without ESIGN/UETA bootstrap.
  • P1.25 hard-gate scope creep (would have inappropriately blocked governance + keystone + library work on the substrate that doesn't apply to them): RESOLVED via EXEMPT clause (Decision 1).
  • Planning-tier disclosure rework duplication (5 per-calculator CCO touches would have been wasteful): RESOLVED via single batched PL1-disclosure seed row (Decision 3).
  • P2.PL1-P2.PL4 sequencing blocker (tax-ref dataset was downstream of first calculator): RESOLVED via reorder (Decision 4); tax-ref ships first.

Operator decisions formalized

All four decisions are CTO/CISO operator-authority (no CCO round-trip required for the decisions themselves; CCO touches remain only on the substantive seed rows enumerated in §3.7 of ROADMAP.md).


2026-05-26 (Monday afternoon — HIGH-2 conversation-lifecycle WORM mirror + M2 chat.message.superseded LIVE; ADR ACCEPTED)

Dedicated session for the HIGH-2 / ROADMAP P1.29 bundle deferred from the morning P0 sequence. 2 estate PRs landed + 1 shared/ doc PR closing the conversation-lifecycle WORM gap + the CCO-approved M2 chat.message.superseded immutable audit row. Compliance-bearing — closes the books-and-records gap for the advisor-chat surface's state-changing principal mutations (shared-session lifecycle + message supersession).

M2 implementation (pw-os-v2 #395) — new chat.message.superseded audit_log row at the edit/regenerate handler, written before supersedeMessagesFrom with fail-closed semantics (writeAuditLog throws → supersedeMessagesFrom never runs; route 5xxs). afterState carries conversation_id + source_message_id + superseded_count + per-message {message_id, role, content_sha256} array + replacement_content_sha256 + replacement_kind ('edit' | 'regenerate'). Content hashes only — never the full message text (PII stays in messages under existing 7-year retention; audit_log carries the tamper-evident reconciliation trail without duplicating bodies).

HIGH-2 conversation-lifecycle WORM mirror (same pw-os-v2 #395) — parallel writeAuditLog() calls land alongside the existing writeAiAudit() at the 3 sites in apps/api/src/routes/chat.ts (POST /share → conversation.shared_with; DELETE /share/:email → conversation.participant_revoked OR conversation.participant_left). Mirrors the 2026-05-23 dedicated decision-rationale audit row pattern (memory dedicated-decision-rationale-audit-row-pattern). Same posture as pre-existing writeAiAudit (post-state-change audit).

Canonical-registry alignment (pw-api #279)CHAT_MESSAGE_SUPERSEDED: 'chat.message.superseded' registered in pw-api's canonical PW_ACTIONS taxonomy. The 3 conversation.* verbs are 2-segment and stay pw-os-v2-only (pw-api's strict 3-segment PW_ACTIONS invariant doesn't admit them; pw-api never emits them either — asymmetry is benign).

ADR promotion (DRAFT → ACCEPTED)shared/architecture/decisions/ADR-chat-message-superseded-audit-row.md promoted 2026-05-26 with as-shipped section. Both promotion criteria satisfied (CTO/CISO as-shipped review via pw-os-v2 #395 + pw-api #279; CCO Reg S-P / Rule 17a-4 / 204-2 review satisfied 2026-05-20). Bundled conversation-lifecycle scope covered under the same CCO authorization umbrella per memory cco-gate-scope-substrate-vs-client-facing-2026-05-19.

Two dispatch-vs-code-shape divergences caught + resolved in flight via AskUserQuestion:

  1. The dispatch said "widen audit_log_action_check CHECK constraint" — verified that audit_log.action has NO CHECK constraint in either repo (the migration-0046 CHECK-widening pattern lives on pw-os-v2's ai_audit_log, not audit_log). No migration shipped in this bundle; adding a verb is TypeScript-only.
  2. The dispatch said add 4 verbs to pw-api's PW_ACTIONS — verified that 3 are 2-segment (violates pw-api's strict 3-segment invariant). Operator confirmed Option 1 (add only CHAT_MESSAGE_SUPERSEDED to pw-api PW_ACTIONS; conversation.* verbs stay pw-os-v2-only).

Estate PRs (2 + 1 shared/)

Repo PR Title
pw-api #279 feat(audit-actions): register chat.message.superseded canonical verb (HIGH-2 / M2)
pw-os-v2 #395 feat(audit-log): wire conversation lifecycle + chat.message.superseded WORM rows (HIGH-2 / M2)
shared (this entry) docs: HIGH-2 / M2 conversation-lifecycle + chat.message.superseded LIVE — ADR ACCEPTED + COMPLIANCE-CHANGELOG + ROADMAP P1.29 closed

Coverage closed

  • HIGH-2 (conversation-lifecycle WORM gap surfaced 2026-05-25 evening diagnostic) — CLOSED.
  • M2 (chat.message.superseded, ADR DRAFT since 2026-05-20) — CLOSED + ADR promoted DRAFT → ACCEPTED.
  • ROADMAP P1.29 (deferred to dedicated session per overnight-2026-05-25/CLOSING-SESSION-RECOMMENDATIONS.md) — moved to Recently SHIPPED at P1.
  • ROADMAP P1.21 M2 sub-item (bundled into P1.29 per dispatch) — closed.

Verification

  • pw-api #279: 1664/1664 unit tests pass; tsc + lint clean; CI all-green (Lint+typecheck+test + Migration dry-run + Gitleaks + Trivy + npm audit) — merged 15:08Z.
  • pw-os-v2 #395: 1439/1439 unit tests pass; typecheck + lint clean; auto-merge pending CI.
  • Post-deploy smoke: operator can verify by triggering a self-share at pwos.app/chat → Share button, then querying audit_log for action='conversation.shared_with' at /admin/compliance/audit-log.

2026-05-26 (Monday — iteration close: P0 substrate fully landed + GitHub abuse-heuristic suspension incident + resolution)

Morning P0 + doc-refresh + 3 audience-segmented briefs + cross-repo PII_TAGS triplet ALL LANDED — 4 morning compliance findings closed (HIGH-1 + MEDIUM-1 + MEDIUM-2; HIGH-2 deferred to dedicated session per CLOSING-SESSION-RECOMMENDATIONS.md as M effort). 7 estate-wide PRs merged + 4 supporting shared/ PRs across pw-api + pw-os-v2 + pw-portal-v2 + pw-infrastructure + shared/. The 2026-05-25 overnight unattended cleanup run's 21 deliverables fully consumed.

Suspension incident at ~12:18Z + resolution. During the rapid P0 push-burst (5 PR opens + auto-merge enables across 3 repos in ~2 minutes), all PR-triggered CI started failing on actions/checkout@v6 with remote: Your account is suspended + HTTP 403. Root cause was a transient GitHub abuse-heuristic restriction — per-token throttle, not an account-level suspension. Resolved without GitHub Support escalation via: (a) fresh pull_request trigger on each blocked PR (close/reopen — operator-side close/reopen cancels auto-merge so each had to be re-merged manually after the fresh CI run); (b) credentials rotated to SSH + refreshed gh OAuth under single identity rivendale; (c) PW_POSTURE_PROBE_PAT re-minted (older PAT was implicated in the abuse-heuristic burst); (d) GH_PAT_PW_API survived the rotation (lives as the pw-infrastructure CI cross-repo pw-api typecheck fetch surface — must NOT be revoked without re-mint per the new pw-infra-workflow-pats-active-dont-sweep memory). 3 new memory artifacts codified the prevention + recovery + workflow-PAT-inventory discipline; a CLAUDE.md §1.4 addendum proposal staged at shared/ideas/2026-05-26-claude-md-1-4-addendum-throttle-and-fresh-trigger.md for operator review (NOT auto-landed — structural change per existing §1.4 gate).

HIGH-1 closure end-to-end — pw-api #278 (canonical-add for 5 intern_* tables) + pw-os-v2 #394 + pw-portal-v2 #81 (byte-equal mirrors of pw-api canonical post-#278-merge, drift gate validated at sha cdf54607…); the per-repo apps/api/scripts/check-pii-tags-drift.mjs gate validated each mirror against the post-#278 pw-api/main canonical and passed clean. Closes ROADMAP P1.28.

MEDIUM-1 closure — pw-infrastructure #204 extends .gitignore glob *.tfplan*.plan covering text-form plan captures (caught pre-incident; the 102KB cloud-run-baseline/docs.plan + 19KB service-accounts/sa.plan files were untracked at risk of git add -A capture per the 83f346b incident class). Closes ROADMAP P1.30.

MEDIUM-2 closureshared #377 prepended 2 Rule 204-2 audit-trail entries to compliance/COMPLIANCE-CHANGELOG.md: 2026-05-22 CES Implementation Phase 1 substrate merged (inert against empty table) + 2026-05-23 Doc-pipeline Slice 1+2 V1 LIVE + PRODUCTION-VERIFIED (5-control CCO-perimeter ratified). Closes ROADMAP P1.31.

Doc-refresh PR (shared #378) landed 6 B?-DRAFTs + 3 architecture refresh files in one PR: CHANGELOG 2026-05-26 entry + CURRENT-STATE 2026-05-26 anchor (Z-EVALUATION 5-edit micro-patch, NOT full-section rewrite — preserved ~250 lines of cascade content) + ROADMAP P1.28/P1.29/P1.30/P1.31 added + deadline countdown (8 days) + BlockSkunk Phase 0 status check + README Firm State header bump + NEXT-PROMPT.md in ASCII layout + pw-learnai added to Related repos + CLAUDE.md §6 + §6.1 + §1.4 + §2.0.1 + §8.1 patches + Last-Verified header bump + BLUEPRINT.md drop-in replace (refreshed to 2026-05-26 state with new §6 in-perimeter doc-conversion sidecars + cross-reference replacing stale PW-STATE-2026-05-11.md with shared/architecture/PW-STATE.md) + PW-STATE.md NEW canonical universal-team brief (14 sections; readable in 15 minutes) + PWOS.md v1.5 → v1.6 (new §8.10 nighttime diagnostic findings + GitHub Actions suspension narrative + new §19 full-LLM-context handoff for fresh Claude instances).

Audience-segmented dated briefs (shared #379) landed PW-PUBLIC-2026-05-26.md (Marketing-Rule §206(4)-1-safe) + PW-INTERNAL-2026-05-26.md (interns + contractors + prospective hires under NDA) to strategy/. PW-FOUNDERS-2026-05-26.md staged on disk for operator-commit-only path at shared/governance/ per CLAUDE.md §1.3.

NEXT-PROMPT.md refreshed (shared #376) with the 5/26 AM cold-start prompt referencing the overnight's 21 deliverables; the iteration close ritual will overwrite again at end of next iteration for the 5/27 AM session.

Estate-wide PRs (7 + 4 shared/)

Repo PR Title
shared #376 docs(next-prompt): refresh for 5/26 AM — overnight run completed
shared #377 compliance(changelog): 2026-05-22 CES Phase 1 + 2026-05-23 doc-pipeline Slice 1+2 LIVE — Rule 204-2 audit-trail entries
shared #378 docs: 2026-05-26 morning doc-refresh — CHANGELOG + CURRENT-STATE + ROADMAP + README + CLAUDE + BLUEPRINT + PW-STATE + PWOS v1.6
shared #379 docs(audiences): 2026-05-26 dated briefs — PUBLIC + INTERNAL snapshots
pw-api #278 feat(pii-tags): canonical-add for 5 intern_* tables (HIGH-1)
pw-os-v2 #394 feat(pii-tags): byte-equal mirror of intern_* canonical blocks (HIGH-1)
pw-portal-v2 #81 feat(pii-tags): byte-equal mirror of intern_* canonical blocks (HIGH-1)
pw-infrastructure #204 chore(gitignore): extend *.tfplan → *.plan glob (MEDIUM-1 pre-incident)
shared (this entry) docs(state): 2026-05-26 iteration close — P0 complete + suspension incident + resolution

Memory artifacts (3 new)

Artifact Purpose
github-actions-suspended-403-fresh-trigger-not-rerun Fresh-trigger via close/reopen on a CI "account suspended" 403, never gh run rerun
throttle-automated-git-ops-to-avoid-abuse-heuristic Pace multi-PR push-bursts (30-60s between opens + auto-merge enables) to avoid the abuse heuristic
pw-infra-workflow-pats-active-dont-sweep GH_PAT_PW_API + PW_POSTURE_PROBE_PAT inventory — re-mint, don't sweep during rotation

Proposed (NOT auto-landed — operator review)

Path Purpose
shared/ideas/2026-05-26-claude-md-1-4-addendum-throttle-and-fresh-trigger.md CLAUDE.md §1.4 addendum DRAFT codifying the 3 memory artifacts as load-bearing agent guidance

2026-05-26 (Monday morning — overnight cleanup run completed; doc-refresh PR landing 6 B?-DRAFTs + 3 architecture refresh files)

The 2026-05-25 overnight unattended cleanup run completed; the morning operator lands the produced drafts as a single doc-refresh PR plus 3 P0 substrate PRs for the HIGH-1 PII_TAGS gap + MEDIUM-1 .gitignore glob + MEDIUM-2 COMPLIANCE-CHANGELOG drift. Production was confirmed GREEN throughout the overnight (no incidents, 7 Cloud Run services Ready on 2026-05-23 revisions, 9 scheduler jobs firing, GCS WORM lock re-verified True / 220903200s 7yr). The overnight produced 13 drafts (plus 3 architecture refresh + 3 audience-segmented dated docs added in close-pass = 21 total deliverables at /home/nick/projects/pw/overnight-2026-05-25/) totaling ~392KB; zero git mutations across all 9 estate repos. Three corrections landed via the overnight diagnostic vs the 5/25 evening anchor: (a) DOC_CONVERTER_GOTENBERG_URL + DOC_CONVERTER_DOCLING_URL env vars confirmed LIVE on pw-os-00346-jk4 (corrects the stale memory doc-pipeline-slice-1-live listing wire-up as a pending operator follow-up); (b) actual open dependabot PR count is 22, not 8 (the 5/25 evening anchor pre-dated nexus-core's 10 OSS pip PRs opened the same evening); (c) GCS WORM lock re-verify CONFIRMED in force (carry-forward closes). Two carry-forwards close: GCS WORM re-verify (carry P3.19) + P1.27 governance/review-items POST creation route (shipped 2026-05-23 evening via pw-api #276).

Morning P0 substrate sequence (in flight at time of this entry):

  • PR 1 — shared/ NEXT-PROMPT.md refresh for the 5/26 AM cold-start prompt (overwrote the 5/25 evening stale version that still referenced the overnight as queued/if-the-run-completed).
  • PR 2a — pw-api PII_TAGS canonical-add for the 5 intern_* tables (HIGH-1; closes the grep "intern_" pw-api/src/lib/pii-tags.ts = 0 matches gap from the overnight verification table). Same-day byte-equal mirror to pw-os-v2 + pw-portal-v2 ships in immediate follow-on PRs per memory pii-tags-cross-repo-sync-required-on-canonical-add to keep the drift gate green for downstream consumer PRs. ROADMAP P1.28.
  • *PR 2b — pw-infrastructure .gitignore .plan glob fix (MEDIUM-1, pre-incident — caught before the next git add -A accident; ROADMAP P1.30).
  • PR 2c — shared/ COMPLIANCE-CHANGELOG entries for 2026-05-22 CES Phase 1 + 2026-05-23 doc-pipeline Slice 1+2 LIVE — Rule 204-2 audit-trail surface restored (MEDIUM-2; ROADMAP P1.31).
  • PR 3 — shared/ doc-refresh (this PR) landing the overnight's 6 doc drafts (B1 CHANGELOG / B2 CURRENT-STATE / B3 ROADMAP / B4 README / B5 CLAUDE.md / B6 NEXT-PROMPT already done in PR 1) plus 3 architecture refresh drafts (H1 BLUEPRINT drop-in / H2 PW-STATE.md new file / H3 PWOS.md v1.5 → v1.6 patches).
  • PR 4 — shared/ audience-segmented dated docs (H4 PW-PUBLIC + H5 PW-INTERNAL to shared/strategy/; H6 PW-FOUNDERS staged for operator commit to shared/governance/ per CLAUDE.md §1.3 operator-commit-only path).

HIGH-2 (conversation-lifecycle WORM mirror with M2 bundle) stays deferred to a dedicated session per CLOSING-SESSION-RECOMMENDATIONS.md — M effort (3-4 hours wall-clock); not crammed into the morning sequence.

Operational follow-ups carried to next iteration (per overnight P2-P4): 22-PR dependabot sweep (15 CLEAN bulk auto-merge candidates: pw-api #277 + pwos-core #41/#42 + nexus-core #34-#43; 5 manual triage including pw-portal-v2 majors; 3 pw-website Tests-class single-root-cause investigation; 2 stale BEHIND @dependabot recreate); shared/ archival sweep (31 archive-candidates from A-CLEANUP-PLAN.md); _reference/ cleanup (5 malformed symlinks); Cloud Scheduler tick wire-up gap for KYC + risk-tolerance crons (confirm intended trigger); pwportal.app design-system v2.0 migration (ROADMAP P2.17).

Reg S-P / Reg XP June 3, 2026 deadline countdown: 8 days. The 3 CCO DocuSign signatures + vendor 72-hour clause closure remain CCO-led.

Estate-wide PRs (morning sequence)

Repo PR Title
shared #376 docs(next-prompt): refresh for 5/26 AM — overnight run completed
shared #377 compliance(changelog): 2026-05-22 CES Phase 1 + 2026-05-23 doc-pipeline Slice 1+2 LIVE — Rule 204-2 audit-trail entries
shared (this PR) docs: 2026-05-26 morning doc-refresh — CHANGELOG + CURRENT-STATE + ROADMAP + README + CLAUDE + BLUEPRINT + PW-STATE + PWOS v1.6
pw-api #278 feat(pii-tags): canonical-add for 5 intern_* tables (HIGH-1)
pw-infrastructure #204 chore(gitignore): extend *.tfplan → *.plan glob (MEDIUM-1 pre-incident)

(pw-os-v2 + pw-portal-v2 PII_TAGS byte-equal mirror PRs queued to push immediately after pw-api #278 lands; numbers not yet allocated.)


2026-05-25 (Sunday evening — nighttime diagnostic + overnight cleanup queued)

Pre-resume nighttime diagnostic returned production GREEN + 2 HIGH compliance findings for 5/26 morning P0 sequencing. Four parallel read-only agents ran a comprehensive estate check after the 2026-05-23 ship train. Production health = GREEN: 7 Cloud Run services Ready on latest 2026-05-23 revisions (pw-api 00265-vql, pw-os 00346-jk4 with 3600s timeout, pw-portal 00088-8c7, gotenberg + docling sidecars internal ingress, pw-api-webhooks, langfuse); 9 Cloud Scheduler jobs firing on cadence (annual ADV cron OK 5/25 03:30Z); 1 benign error in 48h (/.env bot probe on langfuse); Stream Z zero activations; DOC_CONVERTER_GOTENBERG_URL + DOC_CONVERTER_DOCLING_URL env vars LIVE on pw-os-00346-jk4 (corrects the stale MEMORY entry doc-pipeline-slice-1-live that listed env-var wire-up as a pending operator follow-up — it shipped on the 2026-05-23 20:44Z revision). Code health = READY-TO-RESUME with light cleanup: all 5 runtime repos clean on main; 8 dependabot PRs open (pw-api #277 clean ready-to-land; pw-portal-v2 #78/#79/#80 three opened 5/25 incl. quiltt v5→v6 + react-markdown v9→v10 majors; pw-website #108/#110/#121 fail tests as a single root-cause class — vitest 4 / lucide major / plugin-react v6). UX = public surfaces healthy (the WSL diagnostic box has no Chrome cookie store so 6 authenticated pwos.app routes — /admin/interns, /admin/altruist, /clients, /clients/import, /chat mode selector, advisor markdown — gated to /auth-required as expected; auth-walls work correctly; operator confirmed /admin/interns end-to-end with intern GitHub names added). 3 UX nits surfaced: pwos.app/login returns 404 (root IS the login surface); cold pre-hydration white-flash; pwportal.app still on superseded v1.0 warm-gold brand while pwos.app is on canonical v2.0 blue-centric per docs/firm/design-system.md §11. Security/compliance returned 2 HIGH + 2 MEDIUM:

HIGH-1 — PII_TAGS interns canonical-add gap. pw-api/src/lib/pii-tags.ts has zero entries for the 5 new intern_* tables (engagement / milestone / status_update / evaluation / document) despite the substrate carrying intern_engagement.student_name (TEXT) + intern_evaluation.scores/justification (free-text JSONB) + intern_status_update.shipped/blockers (free TEXT) + *_by_email columns. The writeAuditLog redaction scanner still catches content-pattern PII in audit snapshots but the structural per-table tag is load-bearing for excludeHighPii exports + the partner-facing security posture claim. Per memory pii-tags-cross-repo-sync-required-on-canonical-add — adding to canonical needs same-day byte-equal mirror to pw-os-v2 + pw-portal-v2 or the next consumer PR trips the drift gate. Triplet PR queued as morning P0.

HIGH-2 — shared-chat lifecycle WORM gap. New verbs conversation.shared_with / conversation.participant_revoked / conversation.participant_left (pw-os-v2 #384, LIVE 2026-05-22) write only to ai_audit_log via writeAiAudit — non-WORM, non-immutable. Sharing a chat conversation grants RBAC access to another partner; that's a state-changing principal mutation that should land an immutable audit_log row per SEC 17a-4/204-2. Same class as the 2026-05-20 M2 finding (chat.message.superseded, CCO-approved but not shipped). Bundle the conversation-lifecycle WORM emissions with M2 implementation per the dedicated decision-rationale audit row pattern from 2026-05-23 (memory dedicated-decision-rationale-audit-row-pattern).

*MEDIUM-1 — .gitignore .plan glob. pw-infrastructure/.gitignore:14 catches *.tfplan but the local working tree carries terraform/cloud-run-baseline/docs.plan (102KB) + terraform/service-accounts/sa.plan (19KB) — at risk of accidental git add -A capture per the 83f346b incident class. Caught pre-incident. Extend glob to *.plan + delete local files.

MEDIUM-2 — COMPLIANCE-CHANGELOG drift. compliance/COMPLIANCE-CHANGELOG.md ends 2026-05-21. Doc-pipeline Slice 1+2 LIVE deploy 2026-05-23 was CCO-perimeter (5-control guarantee per memory doc-pipeline-slice-1-live). Rule 204-2 audit-trail-of-the-audit-trail entry missing.

Overnight cleanup prompt authored at strategy/2026-05-25-overnight-cleanup-prompt.md — a 4-6h unattended Claude Code session covering: shared/ archival census (strategy/ at 2.3M / 63 entries; diagnostics/ 14 files; dispatches/ 7 dated dispatches), 6 canonical doc drafts (CHANGELOG / CURRENT-STATE / ROADMAP / README / CLAUDE.md / NEXT-PROMPT), 9-repo health pass (active + 3 open-source: pwos-core / nexus-core / pw-learnai), _reference/ disposition (10 deprecated repos), full GCP infra inventory with drift-vs-IaC, MEMORY.md trim plan (currently >24.4KB over the partial-load limit), COMPLIANCE-CHANGELOG drift survey. Morning operator reviews drafts + lands as single doc-refresh PR. NEXT-PROMPT.md authored at shared/NEXT-PROMPT.md for predictable 5/26 wake-up retrieval with P0/P1/P2 sequencing baked in. New finding from the Cloud Run probe: KYC + risk-tolerance cadence crons are absent from Cloud Scheduler — confirm intended trigger mechanism on 5/26.

Estate-wide PRs (1, shared)

Repo PR Title
shared (this PR) docs(state): 2026-05-25 nighttime diagnostic + overnight cleanup prompt + 5/26 NEXT-PROMPT

2026-05-23 (Iteration close — post-Slice-2 onboarding-routes + xlsx/pptx + PWOS.md v1.5)

Four PRs landed across three repos in one parallel-subagent iteration; both pw-os-v2 BFF onboarding 502 gaps closed; doc-pipeline extended to xlsx + pptx via same Gotenberg path; canonical firm-architecture doc refreshed from v1.4 to v1.5 capturing 5 days of post-2026-05-19 shipments.

Day organized around four threads (each its own subagent in parallel):

  1. P1.18 — pw-api advisor HTTP routes for KYC (PR #274). Closes the pw-os-v2 BFF advisor-kyc.ts 502 gap that's been latent since the Component 2 KYC substrate landed 2026-05-18 without its advisor route layer. 3 routes: GET /v1/internal/kyc/sessions (cross-client list with client_id / state / advisor_action / cursor filters; advisor_action='pending' expands to in-flight state set; advisor_action wins over state when both supplied); GET /v1/internal/kyc/clients/:clientId/sessions (per-client timeline created_at DESC, limit 500); POST /v1/internal/kyc/sessions/:sessionId/decision (body {decision: approve|escalate|reject, decision_reason: string (req, max 2000)}; CCO + CFP only; maps approve→approved, reject→rejected, escalate→escalated). New substrate audit verb kyc.session.escalated admitted via the 0046 CHECK-widening pattern — the escalated state had no substrate-action mapping before (vendor-driven flow never lands directly there); the new map entry + LIFECYCLE_ACTION_FOR_STATE entry give advisor-driven escalation a substrate row + the Tier-2 boundary parity with vendor-driven optional_human_review. Dedicated advisor-decision audit row pattern — captures decision_reason in a separate audit_log row alongside the substrate + lifecycle rows from transitionKycSession, so SEC-exam queries by action='kyc.session.approved' surface both the state-machine row + the operator rationale row. PII projection: read routes omit verdict_json + flags_json + vendor_session_id (pii.high per Pick 3 2026-05-19); an unmask path mirroring signed-documents/reveal-signer-email is a separate PR. 1591/1591 tests pass (+29); typecheck + lint clean; auto-merged on green CI.

  2. P1.19 — pw-api advisor HTTP routes for risk-tolerance (PR #275). Sibling closing the advisor-risk-profile.ts 502 gap. 3 routes mirroring P1.18: cross-client list (with bucket filter via COALESCE(advisor_override_bucket, bucket) for effective-bucket matching; limit 1..200); per-client timeline (capped 500); decision (body {decision, decision_reason, override_bucket?}; 4 verbs approve|escalate|reject|bucket_override; role-gated to CCO + CFP + CIO — CIO admitted vs KYC's CCO+CFP set per Pick 4b). The bucket_override path routes through applyAdvisorOverride lib (cross-bucket CCO-cosign gate; CCO actor satisfies the gate themselves) rather than transitionRiskToleranceSession. 4 new substrate audit verbs (risk_tolerance.session.approved/rejected/needs_review + lifecycle onboarding.risk_tolerance.rejected) — the risk-tolerance substrate map was originally sparser than KYC's (only initiated/responses_submitted/scored covered); these give the advisor decision endpoint full substrate + lifecycle coverage. Note: risk-tolerance has no escalated state in migration 0051 CHECK constraint (distinct from KYC); advisor-driven escalation lands needs_review. 1626/1626 tests pass (+35); typecheck + lint clean; auto-merged on green CI. Carried-forward gap: same POST /v1/internal/governance/review-items creation route absence as P1.18 — BFFs degrade gracefully via review_item_error on both escalate + cross-bucket override paths.

  3. Doc-pipeline Slice 2 follow-on — xlsx + pptx MIME support (pw-os-v2 #392). Single-PR extension of the Slice 2 substrate via the same Gotenberg /forms/libreoffice/convert path. apps/api/src/lib/doc-converters.ts introduces an OFFICE_MIMES set (DOCX + XLSX + PPTX); attachments.ts SUPPORTED_MIMES + convertInboundDocs(inbound, classification) routing extended; xlsx + pptx route to Gotenberg for BOTH client-data and b2b-counterparty (binary-extract via Docling isn't semantically useful for spreadsheets/presentations — Gotenberg's PDF output is what advisors want); apps/web/src/components/advisor/ChatPage.tsx SUPPORTED_MIMES + <input accept=...> extended. 8 new tests; no new audit verbs (reuses chat.attachment.converted / conversion_failed). Watched the security-grep gotcha (memory doc-pipeline-slice-1-live lesson #1Bearer [a-zA-Z0-9] byte pattern in test fixtures); avoided cleanly. Auto-merged on green CI.

  4. Architecture refresh — PWOS.md v1.4 → v1.5 (shared #371). Canonical firm-architecture doc was 5 days stale; refreshed to capture 11 substantive shipment threads (substrate-audit remediation; substrate decisions + capability inventory; CES design ACCEPTED + Phase 1; chat model policy; P0 timeout fix; P1 adaptive request mode; pwportal chat UX; Altruist OAuth restore; Wealthbox client-import route + 3 pwos.app evening ships; interns module end-to-end; doc-pipeline Slice 1 + Slice 2 V1; 204-2 Classification Matrix; hygiene train). +57/-7 lines (1312 → 1362). New §8.9 captures per-surface model routing + max_tokens policy + adaptive planner; new §11.10 captures 204-2 frame correction + Phase-1 classification matrix + CES design + canonical 5-control CCO-perimeter pattern; §4.1 expanded to "Seven active services" with the two doc-converter sidecars; §9.4 + §15 cross-reference updates. Style compliance — no personal names in new content (caught + fixed one "Adam-CCO" → "the CCO" reference during authoring); existing names in §2 + §18 (substantive-obligation identification) preserved per the two-tier provenance framework.

New ROADMAP item surfaced: P1.27POST /v1/internal/governance/review-items creation route is missing in pw-api. The BFFs degrade gracefully via review_item_error, but the failed-KYC HITL Tier 2 escalation + risk-tolerance cross-bucket override flows never create the governance review_item needed for CCO-cosign UI surfacing. Substrate exists (state machine + GET routes + vote/action routes); only the CREATION route is missing. S effort; mirrors the KYC/risk-tolerance 3-route pattern minus the timeline route.

Reusable substrate pattern codified: dedicated decision-rationale audit row. When an advisor decision endpoint takes a decision_reason, write a DEDICATED audit_log row capturing the rationale alongside the substrate + lifecycle rows that transitionFooSession already writes. SEC-exam queries by action='kyc.session.approved' (substrate) + action='kyc.session.advisor_decision' (rationale) get both. Shipped in #274 + #275 across KYC + risk-tolerance.

Estate-wide PRs (4 — 1 shared + 2 pw-api + 1 pw-os-v2)

Repo PR Title
shared #371 docs(architecture): PWOS.md v1.5 refresh — 5 days of post-v1.4 shipments
pw-api #274 feat(kyc): wire /v1/internal/kyc/* advisor HTTP routes (P1.18)
pw-api #275 feat(risk-tolerance): wire /v1/internal/risk-tolerance/* advisor HTTP routes (P1.19)
pw-os-v2 #392 feat(chat): extend doc-pipeline to xlsx + pptx (Gotenberg path)

2026-05-23 (Doc-pipeline Sidecars Slice 2 V1 LIVE — pw-os-v2 BFF + frontend integration)

Slice 2 V1 of the doc-pipeline shipped to pw-os-v2 main ~3 hours after Slice 1 landed in pwllc-prod. The advisor-chat BFF (Hono on Cloud Run) now consumes the two in-perimeter Cloud Run sidecars (Gotenberg + Docling) Adam-CCO ratified earlier today via the 5-control CCO-perimeter guarantee. Two PRs landed:

  • pw-os-v2 #390 (BFF integration): new apps/api/src/lib/doc-converters.ts with OIDC-authed HTTP clients for both sidecars (Gotenberg /forms/libreoffice/convert, Docling /v1alpha/convert/file); DocConverterError({kind: unavailable | timeout | rejected | malformed, sidecar, status, detail}) carries the fail-loud signal. attachments.ts extended with docx in SUPPORTED_MIMES + new convertInboundDocs(inbound, classification) routing — client-data → Gotenberg fidelity → PDF → Claude PDF content block; b2b-counterparty → Docling text-extract → text-prepend (markdown). Two new audit verbs admitted via the 0046 CHECK-widening pattern: chat.attachment.converted (afterState carries sidecar / source_mime / output_mime / source_bytes / output_bytes / duration_ms / source_name / output_sha256) + chat.attachment.conversion_failed (afterState carries kind / status / detail / sidecar). The chat send route runs conversion AFTER validateInbound + BEFORE the per-conversation byte cap so the cap measures the bytes that actually reach Claude (a small docx can produce a larger PDF). Post-conversion size cap re-check added — a docx that expands past MAX_FILE_BYTES post-convert 413s with a kind: 'output_too_large' audit row before materializeAttachments' post-decode guard would 5xx. On any DocConverterError: 502 to caller + audit row capturing the kind + status. Two pre-merge CI fix-forwards on the same branch: (a) the repo's Security check workflow greps Bearer [a-zA-Z0-9] across apps/**/*.ts excluding __tests__/ only, and my 'Bearer mock-oidc-token' test literal tripped the grep — rewrote the assertion to split(' ') so the literal "Bearer " pattern no longer appears in the test file (same coverage); (b) the post-conversion size cap re-check landed in the same fix-forward commit. 1420 api tests + full-repo typecheck green; ESLint + compliance lint clean.
  • pw-os-v2 #391 (frontend accept): ChatPage SUPPORTED_MIMES set + <input accept=...> extended to docx. This PR was orchestrated separately because the frontend commit raced the auto-merge of #390 (auto-merge fired the moment the security-check pass landed, before the frontend push landed). #391 carries only the frontend change; the BFF half is in #390. CI had one transient flake on the Tests job (actions/checkout@v6 auth failure — fatal: could not read Username for 'https://github.com') that was re-run successfully.

UPDATE — Slice 2 V1 FULLY WIRED + PRODUCTION-VERIFIED (~30 min after #391 merged). pw-infrastructure #203 authored + operator-merged + operator-applied — adds DOC_CONVERTER_GOTENBERG_URL + DOC_CONVERTER_DOCLING_URL env vars on the pw-os Cloud Run service template, sourced from the sibling sidecar resources in the same workspace (so an image bump that re-issues a revision URL flows through automatically on the next apply). One TF apply contention worth capturing: the post-merge CI Terraform plan job acquired the GCS state lock while the operator's plan was queueing → operator got Error 412 conditionNotMet → lock released cleanly when CI completed → operator re-planned + applied successfully. The 6-resource modify (1 functional + 5 scaling-block reconciliation no-ops matching the Slice 1 pattern) completed. New pw-os revision pw-os-00345-pvx serving 100% traffic with both env vars present (verified via gcloud run services describe). Operator smoke-tested at pwos.app/chat by uploading a junk-content docx — Claude received it as a PDF content block and read the text layer accurately ("That PDF doesn't have any real content; it's just keyboard mash: 'Glnngjgmer / Geklgjklfbjgf...'"); end-to-end proof: frontend accept → OIDC mint → Gotenberg /forms/libreoffice/convert → %PDF magic check → Claude PDF content block → text-layer preserved. The whole conversion ran inside the project perimeter — bytes never left the GCP network. P1.26 (the env-vars operator follow-up) SHIPPED 2026-05-23 evening.

Deferred Slice-2 follow-ons (P1.22b): Drive/gdocs resolution (export-as-docx via the existing DWD-impersonated Drive client → sidecar pipeline) + per-conversion PII canary on Docling-extracted text (the b2b-counterparty path is canary-overridden by attestation; client-data → Gotenberg → PDF inherits the existing PDF non-scannable posture per routes/chat.ts "attachments are NOT PII-scanned in v1" comment).

Trust posture (unchanged from Slice 1): the sidecars run with all 5 CCO-ratified controls. Slice 2 consumes that perimeter; it does NOT introduce new external dependencies.

Estate-wide PRs (3 — 2 pw-os-v2 + 1 pw-infrastructure)

Repo PR Title
pw-os-v2 #390 feat(chat): doc-pipeline Slice 2 — sidecar integration for docx attachments
pw-os-v2 #391 feat(chat-ui): accept docx in advisor file picker
pw-infrastructure #203 feat(pw-os): wire DOC_CONVERTER_{GOTENBERG,DOCLING}_URL on pw-os Cloud Run

2026-05-23 (Doc-pipeline Sidecars Slice 1 LIVE + Overnight Diagnostic + 204-2 Classification Matrix + Hygiene Train)

Eight PRs landed across five repos in one iteration; two sidecars went from greenfield to LIVE in pwllc-prod; the cross-repo 204-2 books-and-records posture was scope-corrected with a CCO-ratified classification matrix. Day organized around four threads:

  1. Doc-pipeline Sidecars — Slice 1 LIVE (Gotenberg + Docling, in-perimeter). pw-infrastructure #201 shipped the TF DRAFT (two google_cloud_run_v2_service resources + shared pw-doc-converter-service SA + scoped run.invoker IAM). Image defaults digest-pinned to operator-verified revisions (gotenberg/gotenberg:8@sha256:a40c5a46… v8.32.0 MIT-licensed; docling-project/docling-serve:v1.19.0@sha256:ad8259f3… v1.19.0 MIT-licensed). Adam-CCO ratified the 5-control CCO-perimeter guarantee (INTERNAL_ONLY ingress + PRIVATE_RANGES_ONLY egress + zero-vendor-creds + no-persistent-storage + scoped-invoker) via a bare "approved" PR comment at 2026-05-23T15:48:48Z; operator merged 6 minutes later. Operator's initial terraform apply failed on the Docling create — Cloud Run's image-pull host allowlist rejects quay.io directly (only gcr.io / docker.pkg.dev / docker.io accepted). pw-infrastructure #202 fix-forward added a google_artifact_registry_repository.quay_remote (REMOTE_REPOSITORY mode proxying https://quay.io) + IAM grant to the Cloud Run service agent + re-routed the Docling default through the AR coordinate. Operator's re-apply succeeded; both sidecars are LIVE (Ready=True) at https://pw-doc-converter-gotenberg-hifyv7pcxq-uc.a.run.app + https://pw-doc-converter-docling-hifyv7pcxq-uc.a.run.app. Books-and-records record at compliance/cco-approvals/PW-DOC-PIPELINE-SLICE-1-2026-05-23-APPROVAL.md. Verification finding: Cloud Run v2's INTERNAL_ONLY denial returns 404 (not 403) — /health probes from external + Cloud Shell sources all return 404 by design (security-by-obscurity variant). Startup probe Ready=True confirms /health IS responding 2xx from inside Cloud Run's authorized network. Memory: doc-pipeline-slice-1-live, cloud-run-host-allowlist-rejects-quay-needs-ar-remote-mirror, cloud-run-internal-only-returns-404-not-403-for-denial.

  2. Overnight Diagnostic + 204-2 Classification Matrix (the books-and-records reframe). A read-only overnight diagnostic ran across the estate (no code-repo writes; 10 findings docs landed at strategy/diagnostics/2026-05-23-overnight/ via #364). The dominant finding (B4-1) scope-corrected an existing governance-ledger entry — the Altruist-callback "WORM-mirror gap" was actually estate-wide (~50% of audit_log rows PG-only because writeAuditMirror() lives only in pw-api/src/lib/audit.ts; 46 pw-os-v2 BFF sites + 1 pw-portal-v2 + 3 raw-SQL pw-api sites bypass). A Phase-1 read-only classification matrix landed via #365 — every distinct audit class (320 total) classified into one of three buckets (RECORD_204_2 / EVIDENCES_204_2 / OPERATIONAL) with proposed-bucket + empty cco_ratified_bucket columns. Headline carve-out: of the 100 bypassed classes, 71% are OPERATIONAL telemetry with no 204-2 archive obligation (per-chat-turn agent.context.*, ai.tool.called, auth events, etc.) — the real archive gap is 29 classes (7 RECORD_204_2 + 22 EVIDENCES_204_2). Regulatory frame corrected: Protocol Wealth is a standalone SEC RIA under Advisers Act Rule 204-2 (reasonable controls), NOT a broker-dealer under Exchange Act Rule 17a-4 (WORM media); the GCS bucket is a best-practice control we hold ourselves to, not a per-se requirement. CISO determination on remediation path + NORMAL timeline DATED 2026-05-23; CCO ratification block intentionally EMPTY + UNDATED for Adam's review. Two CISO-side governance-ledger determinations CLOSED: Interns 1099-NEC classification (CTO/CISO-autonomous; internal tooling; no client NPI) + B5-1 PII_TAGS drift gate hardening (engineering substrate). Memory: worm-mirror-coverage-gap-is-estate-wide, market-scan-profile-source-sector-never-in-schema.

  3. B5-1 PII_TAGS cross-repo drift fix (HIGH). pw-portal-v2 #77 hardened the PII_TAGS cross-repo drift gate. The portal had 3 missing table blocks vs the pw-api canonical (account_balances from migration 0055; signed_document_archive + signed_document_envelope_events from migration 0053; chat_sessions from migration 0054 — the diagnostic counted 2, reality was 3). Root cause: the portal's pre-fix CI gate invoked the bash drift script against raw.githubusercontent.com which 404s on private repos without auth; the CI step degraded that exit-2 to a non-blocking warning, so drift landed silently between every pw-api canonical update and the next portal PR. Fix mirrors pw-os-v2 exactly: new apps/api/scripts/check-pii-tags-drift.mjs Node wrapper using GitHub Contents API + Bearer auth (private-repo compatible), delegates to the vendored bash script via PII_TAGS_CANONICAL_URL=file://.... Three missing blocks ported byte-aligned with pw-api canonical. Gate-trips-on-drift verified locally + by CI gate green. Memory: pii-tags-portal-drift-gate-checks-fixture-not-cross-repo (updated with corrected mechanism).

  4. Hygiene train (gate-free; 3 bundled-PR items). pw-os-v2 #389 bundled three items in one PR: (a) /admin/interns hub-link card added to AdminHubPage TILES (GraduationCap icon); (b) static /auth-required message page (operator-scoped to static copy only — no auto-redirect-to-login, no returnTo, no deep-link capture); (c) B1-1 partner-titles centralized in lib/partners.ts as PARTNER_TITLES = ['CCO', 'CIO', 'CTO'] + hasPartnerTitle() helper; chat.ts:isPartner now delegates. B1-1 flagged finding: the 3-title chat-share gate IS load-bearing, NOT drift — chat.test.ts:155-156 explicitly asserts isPartner({titles:['CFP']}) === false + isPartner({titles:['CISO']}) === false. The PR closes the cross-FILE drift inside pw-os-v2 but preserves the cross-REPO 3-vs-5 distinction (chat-share narrower than pw-api governance 5-title list) as intended design. pw-api #273 shipped B3-1 (evm.ts API-key reads through Zod-validated env instead of raw process.env). B2-1 (oauth_state_nonces RLS) was stopped + flagged — the diagnostic finding was incorrect; the table is intentionally NOT RLS-scoped per migration 0014 + integrations.ts:695 (webhooks consume path has no session context). Branch sweep: 4 stale pw-infrastructure post-merge branches deleted; pw-website's 3 dependabot PRs (#108/#109/#110) skipped per brief.

Estate-wide PRs (8: 5 shared, 1 pw-portal-v2, 1 pw-os-v2, 1 pw-api, 2 pw-infrastructure)

Repo PR Title
shared #364 docs(diagnostics): land 2026-05-23 overnight diagnostic + scope-correct WORM gap
shared #365 docs(governance): Phase-1 204-2 audit classification matrix + close 2 CISO determinations
shared #366 docs(cco-approvals): capture Adam-CCO doc-pipeline Slice 1 approval + close in dispatch queue
pw-portal-v2 #77 fix(pii-tags): port missing canonical blocks + harden cross-repo drift gate
pw-os-v2 #389 chore(hygiene): /admin/interns hub link + auth-required page + B1-1 partner-titles centralized
pw-api #273 chore(hygiene): B3-1 — route evm.ts API-key reads through Zod-validated env
pw-infrastructure #201 feat(doc-converter): Slice 1 TF DRAFT — Gotenberg + Docling in-perimeter sidecars (CCO ratified)
pw-infrastructure #202 fix(doc-converter): route Docling image through AR remote-mirror (Cloud Run rejects quay.io)

2026-05-23 (Interns module — end-to-end LIVE: pw-api substrate + pw-os-v2 UI)

The Interns module shipped end-to-end across two repos — a 1099-NEC contractor engagement tracker for the 2026 summer interns, with a load-bearing FEE-model invariant encoded in the schema (forbidden-columns CI test), milestone "Mark paid" as a STATUS-ONLY record (no payment rail), and a status-only document checklist (no SSN/EIN/W-9 content fields). pw-api #271 shipped the substrate: migration 0057 added 5 intern_* tables (engagement, milestone, status_update, evaluation, document) with tenant-RLS ENABLE+FORCE, a classification CHECK ('independent_contractor') invariant, 7-year retention_until default; 7 canonical intern.* PW_ACTIONS verbs (engagement.created/updated, milestone.accepted/paid, evaluation.recorded, status.received, document.received) admitted via the migration-0046 CHECK-widening pattern; 9 routes under /v1/internal/interns/* mirroring the FEE-model service with a deliberate FLAT /documents/:docId path (globally-unique docId, not nested under engagement); deterministic 15-business-day payment-due-date helper (lib/business-days.ts); seed of both engagements with total_fee_cents = 250000, midpoint 100000 / final 150000 / architecture_proposal 0; load-bearing intern-migration-forbidden-columns.spec.ts blocks any column name from {wages, hourly_rate, hours_per_week, withholding, payroll, salary, benefits, W-2, FICA, PTO, ssn, ein, itin, tax_identifier} from appearing in any intern_* table. Deployed as pw-api-00261-7qw. pw-os-v2 #388 shipped the consumer: BFF at /api/advisor/interns/* proxying the 9 producer routes 1:1 (including the FLAT documents path); isPartner directly imported from chat.ts (no lib/role-guards.ts extraction — direct import resolved cleanly, no circular dep) gating every mutation; /admin/interns React roster + /admin/interns/$engagementId detail page with editable github_username Day-1 backfill, milestone Accept/Mark-paid actions, weekly status-updates log, 5-dim evaluation rubric editor (technical / systems / communication / judgment / initiative; 1-5 scores), document checklist surfacing ONLY status / doc_kind / received_at / records_pointer (no file-upload UI); UI guards encoded — "Records-only — no payment rail" banner on the detail page + "Records payment; does NOT initiate a transfer" subtitle on the Mark-paid button (the FEE-model classification guard visible in the UI). 28 new BFF tests; suite at 1397 pass / 11 skip. Deployed as pw-os-00341-mw2. The orchestrator pre-verified the contract pinned in cli2's dispatch by reading pw-api's src/routes/interns.ts from origin/main byte-for-byte against cli1's archive §8 — one informational divergence noted at runtime: the producer returns 401 (not 400) on missing X-PW-Actor-Email, unreachable from BFF callers since the BFF always attaches the header. Two ritual #7 branch-switch race validations this iteration (cli1 + cli2 both caught + recovered cleanly via the [ "$(git branch --show-current)" = "<branch>" ] && git commit assert-in-commit pattern; this iteration's prevent-not-recover hardening landed in the cli2 dispatch + practiced on the orchestrator-side dispatch PRs themselves). Memory: interns-module-producer-consumer-split + claude-code-branch-switch-race-assert-in-commit.

Estate-wide PRs (2: 1 pw-api, 1 pw-os-v2)

Repo PR Title
pw-api #271 feat(interns): module substrate (1099-NEC contractor engagements)
pw-os-v2 #388 feat(interns): /admin/interns UI + BFF (pw-os-v2 consumer of pw-api #271)

2026-05-22 (Share UX — partner quick-selects on advisor-chat Share dialog)

Partner quick-selects shipped on the async-shared advisor-chat Share dialog — a one-click "Add CCO/CIO/CTO" shortcut beside the email-typed add row, reducing partner-share setup from per-email typing to a single dropdown pick. Built on the 2026-05-22 async-shared substrate (#384 + #385). pw-os-v2 #387 — the Share dialog Partners shortcut surfaces the 3 partner emails (resolved from session.titles) as clickable rows; clicking adds a partner to the conversation in one call, identical wire shape to the typed-email flow. Engineering-substrate; no CCO perimeter.

Estate-wide PRs (1, pw-os-v2)

Repo PR Title
pw-os-v2 #387 feat(chat): partner quick-selects + Partners shortcut on the Share dialog

2026-05-22 (Async-shared advisor chat sessions LIVE)

Async-shared chat sessions shipped — a partner-owner can now share a pwos.app conversation with other partners who read + contribute over time, with per-message attribution. Two pw-os-v2 PRs landed back-to-back: #384 (backend) and #385 (UI). Backend deploy ran migration 0055 successfully — new conversation_participants table (partial-unique on active bindings; partial idx for the sidebar widen); messages.author_email added + backfilled deterministically from each owning conversation's user_email (every pre-share user message was the owner by single-owner invariant); 3 new audit verbs (conversation.shared_with, conversation.participant_revoked, conversation.participant_left) admitted via the migration-0046 CHECK-widening pattern. Read sites in conversations.ts widened consistently via one shared SQL fragment ownerOrParticipantClause() applied at listConversations + getConversation + listMessages; owner-only ops kept (archive / title / tags / session_mode / supersedeMessagesFrom) — participants read + contribute, they do not administer. UI: a Share dialog (owner sees add-a-partner + per-row revoke; participants see self-leave), a Users icon on "shared with me" sidebar rows, an author short-name on user-message turns when it's a different partner, client-side canEdit aligned with the data-layer guard on supersedeMessagesFrom. NOT real-time — no WebSocket fan-out, no presence; existing per-request SSE stream unchanged; other participants see new messages on next load. Sharing partner-only (CCO / CIO / CTO via session.titles + role === 'advisor'); sharee restricted to @protocolwealthllc.com at the BFF. Retention + PII + GCS attachment scoping inherit unchanged (one conversation = one 7-year timer; attachments keyed by conversation_id, so participants gain access to existing attachments automatically). +17 backend unit tests (1369 total). Diagnostic Phase 1.5 (read-only) drove the design.

Estate-wide PRs (2, pw-os-v2)

Repo PR Title
pw-os-v2 #384 feat(chat): async-shared chat sessions — owner + active participants (backend)
pw-os-v2 #385 feat(chat): UI for async-shared chat sessions

2026-05-22 (/admin/altruist client picker truncation fix + /v1/internal/clients/search typeahead)

The /admin/altruist link picker no longer truncates clients past the 50 newest — root cause was the picker (and the /clients page) reading /api/advisor/clients → pw-api /v1/internal/clients/list whose default LIMIT 50, ORDER BY created_at DESC silently dropped everything past the 50 newest rows. With the firm at ~100+ clients (after today's Wealthbox import) most pre-existing clients fell off, including 6 of the imports themselves. Replaced the unsearchable all-clients dropdown with a search-as-you-type combobox backed by a new pw-api typeahead endpoint. pw-api #269 added GET /v1/internal/clients/search?q= — case-insensitive substring ILIKE on first / last / full-name / email, same client universe as /clients/list (deleted_at IS NULL AND client_type != 'Archived'), cap default 30 / max 50, short-q (<2 trimmed chars) returns empty without a DB round-trip; integration test covers each match field + case-insensitivity + cap + short-q + Archived exclusion. pw-os-v2 #383 added the BFF proxy + a reusable ClientSearchCombobox (debounced 250ms, keyboard nav, monotonic-request-id stale-response guard, rows showing name + email + Wealthbox ID) wired into /admin/altruist (replacing the <select>) and into /clients (as a "Jump to a client" header box that reaches every client past the still-capped table). One CI re-run on pw-api #269: the first cross-tenant assertion in the route-level integration test could not hold because the CI integration env connects as the pwapi owner role so RLS is bypassed; rls.spec.ts already owns RLS coverage. Both PRs merged + deployed; operator confirmed "all clients are now showing". Memory: clients-list-limit-50-truncation-and-search, pw-os-v2-route-tests-bypass-rls-in-ci.

Estate-wide PRs (2: 1 pw-api, 1 pw-os-v2)

Repo PR Title
pw-api #269 feat(clients): add /v1/internal/clients/search typeahead endpoint
pw-os-v2 #383 feat(clients): search-as-you-type client picker on /admin/altruist + /clients

2026-05-22 (Wealthbox bulk client import — pwos.app front door)

Wealthbox bulk client import gained its production front door on pwos.app — pw-api #268's import route was unreachable externally (pw-api is INTERNAL_ONLY), so the pw-os-v2 BFF service-account OIDC path is the only working caller. pw-os-v2 #382 added a POST /api/advisor/clients/import-from-wealthbox BFF proxy + a /clients/import dry-run-then-confirm UI: Preview fetches + classifies every active Wealthbox client (writes nothing), renders the count tally (fetched / created / linked / already_present / skipped_no_name / skipped_email_conflict / errors) with a sample table; the Run button stays disabled until a Preview ran this session and shows a confirm dialog before the real run. Operator ran it once at 20:10Z: 56 fetched, 55 created (client_type = 'Client', explicit in the INSERT), 1 linked (kept its pre-import client_type), 0 skipped, 0 errors. pw-api owns the audit row (internal.clients.import_from_wealthbox); no new audit verbs at the BFF. The service-account OIDC pattern reused verbatim from the existing audit-trail / detail / metadata / merge clients-admin routes (Cloud Run metadata-server ID token via fetchIdToken(PW_API_BASE_URL) + X-PW-Actor-Email / X-PW-Actor-Role attestation).

A read-only diagnostic later in the day investigated an operator report that the 56 imports + old portal-clients appeared as "Prospect" in /clients — concluded the 55 imports are correctly client_type='Client' per the import code (every layer of the chain returns the raw column; Cloud Logging confirmed created=55 on the operator's run); old portal-stub clients are 'Prospect' by default per migration 0001 and are not promoted by any UI today (no client_type editing surface on pwos.app — the lifecycle gap is deferred follow-up work).

Estate-wide PRs (1, pw-os-v2)

Repo PR Title
pw-os-v2 #382 feat(clients): pwos.app front door for Wealthbox client import

2026-05-22 (Wealthbox client import — analysis memo + route built + deployed)

The missing path to backfill the pw-api clients table with every active Wealthbox client was analysed, built, and deployed — POST /v1/internal/clients/import-from-wealthbox, behind a dry_run probe. Until now the clients table was populated only by the clients.create tRPC mutation (advisor manual) and the portals/provision create_if_missing branch, so the firm's ~45 active Wealthbox clients were absent from pw-api. The import is built and deployed but not yet run — execution is a deliberate operator action.

Estate-wide PRs (2)

Repo PR Title
pw-shared #354 docs(diagnostics): Wealthbox import-all-clients findings + options memo — MERGED
pw-api #268 feat(clients): import all active Wealthbox clients — MERGED + deployed

What advanced

  • Analysis (shared #354). A read-only investigation confirmed there is no existing bulk-import path and no "portal-only filter" — the gating is structural: the clients table fills only via manual create + portal-provision create_if_missing. Memo: strategy/diagnostics/2026-05-22-wealthbox-import-all-clients-analysis.md. Operator decision locked: import as plain clients rows, with portal provisioning kept decoupled.
  • Route built + deployed (pw-api #268). POST /v1/internal/clients/import-from-wealthbox — fixed filter contact_type=Client + status=Active + type=person (confirmed from the canonical Wealthbox API doc); idempotent upsert keyed on the idx_clients_wealthbox UNIQUE index; email-collision handled (LINK an unlinked email match, SKIP + report an ambiguous one); wealthbox_contact_id set on insert so no wb.contact.create outbox echo fires; no portal / auth / invite side effect. Audited via the canonical CLIENTS_CREATE / CLIENTS_UPDATE verbs with a wealthbox_import origin tag. 13 new tests; CI green; deployed to the pw-api Cloud Run service (revision pw-api-00258-qmg). The stale comment naming a non-existent worker as the sole client-row creation entry point was corrected in passing.
  • dry_run probe. pw-api runs ingress: internal, so a live Wealthbox listContacts cannot resolve from a developer machine; the route carries a dry_run flag that fetches + classifies + returns the plan without writing. The operator runs dry_run first from inside the VPC to confirm the filter + count, then runs for real.

Open operator action

ROWS IMPORTED: 0 — the import has not been run. Execution (dry-run → real) is a deliberate operator action pending an invoke path into the internal-ingress route; a pwos.app admin trigger is a queued follow-on. Memory: pw-api-ingress-internal-unreachable-from-devbox.

2026-05-22 (pwportal resync — diagnostic + scope lock)

The pwportal "Refresh" control was diagnosed: it only re-displays already-stored balances — no on-demand sync runs. The on-demand Quiltt-refresh substrate exists in pw-api but is unwired to the portal control. Diagnostic-only; implementation not built.

Estate-wide PRs (pw-shared)

Repo PR Title
pw-shared #355 docs(diagnostics): pwportal resync has no workflow — diagnostic + confirmed scope — MERGED

What advanced

  • Diagnosis. The portal "Refresh" button re-queries stored balances rather than triggering a Quiltt account refresh; the pw-api on-demand Quiltt-refresh path exists but the portal control was never wired to it. Diagnostic: strategy/diagnostics/2026-05-22-pwportal-resync-diagnostic.md.
  • Scope locked — Option B. The fix wires the portal control to the existing pw-api Quiltt-refresh path and adds a dedicated client-facing audit verb. Cross-repo (pw-portal-v2 → pw-api); client-facing → RIA-compliance gate. Implementation is a separate iteration — not built.

Next

Resync implementation (Option B) is queued. The related client-facing "Disconnect account" feature (pw-portal-v2 + pw-api, CCO-gated) remains queued.

2026-05-22 (Local branch-cleanup sweep — three code repos)

A safe-delete branch-cleanup sweep removed 167 stale local branches across pw-api, pw-os-v2, and pw-portal-v2 — every deletion backed by two independent proofs: a git patch-id match against main plus a merged-PR head-ref cross-reference. The three code repos had each accumulated 45–142 local refs left behind by squash-merged PRs whose remote branches were already pruned; the orphaned refs were tripping fresh sessions into re-anchor cycles.

  • Method. The naive git diff origin/main <branch> empty-tree check is near-useless here — main advances after every squash merge, so the tree diff is never empty. Each candidate was instead proven merged by synthesizing its net patch as a commit on the merge-base and confirming the patch-id is already in main, then independently cross-checked against the repo's merged-PR head refs. A branch was deleted only when both proofs agreed.
  • Result. pw-api 55 → 30 (25 deleted); pw-portal-v2 45 → 13 (32 deleted); pw-os-v2 142 → 32 (110 deleted). Kept: 68 branches with a live remote (open PRs / active work) and 3 with genuinely unmerged local content.
  • One branch held for operator review — pw-portal-v2 docs/promote-adr-b2b-accepted-pattern-8-canonical: its tip is a true ancestor of main (the work landed via PR #72 under a different head ref) so it is safe, but the head-ref cross-reference did not match — held rather than auto-deleted.
  • shared was skipped — its working tree carries operator WIP and 11 active locked agent worktrees; a branch sweep there is deferred to a clean-tree pass.

Housekeeping under CLAUDE.md §1.4 standing authority — no repo-code or remote-branch changes. Queue item "stale merged-branches sweep" updated: pw-api / pw-os-v2 / pw-portal-v2 done; pw-website + pw-infrastructure still pending.

2026-05-22 (Altruist OAuth callback restored)

The Altruist OAuth callback was restored to production — it was never gone, only unreachable: the handler survived the pwdashboard.com retirement intact, but two redirect-URI constants still pointed at the retired domain and the route was mounted under /webhooks/* while Altruist redirects to root /auth/altruist/callback. Investigate-first confirmed the full Altruist integration (handler, token exchange + refresh, AES-256-GCM oauth_connections storage, sync + reconciliation/status endpoints, 6 secrets) survived the retirement — the fix is a narrow wiring correction. pw-api #267 merged + deployed + production-verified; operator confirmed a live Connect-Altruist round-trip end to end.

Estate-wide PRs (1, pw-api)

Repo PR Title
pw-api #267 fix(altruist): restore OAuth callback redirect URI + route mount — MERGED + deployed + verified

What advanced

  • Premise corrected. The originating dispatch's premise ("route handler unregistered") was off — Phase-1 investigation found the 285-line handler complete; 5 of 6 audited areas fully present, only the route registration effectively broken.
  • Single redirect-URI constant. ALTRUIST_REDIRECT_URI consolidated in altruist-client.ts at webhooks.protocolwealthllc.com/auth/altruist/callback; integrations.ts + internal.ts import it instead of holding stale pwdashboard.com copies. redirect_uri added to the code→token exchange (RFC 6749 §4.1.3).
  • Route re-mounted. The callback moved to a dedicated altruistOauthRouter mounted at root /auth/altruist/callback; the LB already forwards /auth/* to the webhooks Service, so no infrastructure change. The old /webhooks/auth/altruist/callback path is retired.
  • Audit aligned. Success audit uses PW_ACTIONS.ALTRUIST_OAUTH_CONNECTED; altruist.oauth.failed is now emitted on token-exchange / persist failures. Two latent audit-SQL bugs fixed in passing (a $3::uuid-into-TEXT cast; a NULL resource_id NOT-NULL violation caught by the new integration test on first CI).
  • Tests. New unit + integration coverage for the callback flow (restores the deleted TODO #147 coverage).

Compliance note

The callback writes audit via the raw write_audit_log SQL function (post-commit, by design) — this bypasses the GCS WORM mirror the writeAuditLog helper performs. Pre-existing; now operationally live. Captured as a Rule 17a-4 fast-follow for Adam-CCO routing — strategy/diagnostics/2026-05-22-altruist-worm-mirror-gap.md.

Next

Three follow-on workstreams seeded (strategy/diagnostics/2026-05-22-altruist-followon-context.md): import all active Wealthbox clients (pw-api); Quiltt/Altruist duplication → client-facing disconnect (pw-portal-v2 + pw-api); pwportal resync has no workflow (pw-portal-v2). The WORM-mirror fast-follow routes to Adam-CCO. mail-auth Stream A + the posture/display super-prompt remain queued. Memory: pw-api-webhooks-lb-path-routing, altruist-integration-intact.

2026-05-22 (advisor-chat P1 — conservative-power adaptive request mode)

P1 shipped the conservative-power adaptive request mode for advisor chat — a deterministic per-turn planner that picks the model, output-token ceiling, and effort level for every chat turn, with no per-turn LLM router on the hot path. Built on the #378 P0 fix; three pw-os-v2 PRs merged, deployed, and verified live on the pw-os Cloud Run service.

Estate-wide PRs (3, pw-os-v2)

Repo PR Title
pw-os-v2 #379 feat(chat): planRequest — per-turn adaptive model/effort planner — MERGED + deployed
pw-os-v2 #380 feat(chat): wire planRequest into chat routes + claude-client effort param — MERGED + deployed
pw-os-v2 #381 feat(chat): advisor chat-mode selector (Adaptive / Max power / Fast) — MERGED + deployed

What advanced

  • planRequest planner (#379). apps/api/src/lib/plan-request.ts — a pure, deterministic per-turn planner returning { modelAlias, maxTokens, effort } from cheap signals. Conservative-power policy: default to full power (Opus 4.7); step down to Sonnet 4.6 only for a near-certainly-trivial prompt (exact-match allowlist, ~99% bar) with no document; any document — attached this turn or anywhere in history — forces full power (Opus 4.7 / xhigh). 19 unit tests cover every branch.
  • Wiring + effort parameter (#380). Both chat send paths (/messages + /edit) plan each turn via planRequest; claude-client.ts streamMessage now sends the Anthropic output_config.effort parameter. Effort confirmed against the authoritative docs: output_config: { effort: "low"|"medium"|"high"|"xhigh"|"max" } — GA, no beta header; xhigh is Opus-4.7-only; high is the API default. The maxTokens clamp keeps #378's 32000 ceiling and binds only as a context-window safety net.
  • Advisor mode selector (#381). A compact Adaptive / Max power / Fast selector replaces the now-vestigial static model selector; the choice is persisted per conversation and sent as modeOverride on every send + edit. Default is Adaptive.
  • The PII egress canary, #378's error-surfacing, and the 32000 max_tokens default are unchanged; CI green throughout (1344 tests on the wiring PR).

Next

mail-auth Stream A (pw-api) + the posture/display super-prompt (pw-api → pw-os-v2) remain queued. Memory: effort-param-and-conservative-power-planrequest.

2026-05-22 (advisor-chat P0 timeout fix — Cloud Run 60s→3600s + chat error-hardening)

The advisor-chat multi-document review failure was root-caused to a 60-second Cloud Run request timeout that killed streaming Opus 4.7 responses mid-stream — fixed by raising the pw-os + pw-portal Cloud Run request timeout to 3600s, with chat error-hardening shipped behind it. The originating dispatch's premise (a context-window / max_tokens problem) was wrong: 1M context is native to Claude Opus 4.7 with no beta header, and the SSE parser was already correct — both verified against the authoritative Anthropic docs + the code.

Estate-wide PRs (2)

Repo PR Title
pw-infrastructure #199 fix(cloud-run): raise pw-os + pw-portal request timeout to 3600s — MERGED + applied
pw-os-v2 #378 fix(chat): surface specific stream errors + lower max_tokens to 32000 — MERGED + deployed

What advanced

  • P0 root cause + fix (pw-infrastructure #199). terraform/cloud-run-baseline/cloud-run-services.tf set timeout = "60s" on the pw-os and pw-portal Cloud Run services ("task #29 V1 stability"). A streaming advisor-chat response — a multi-document review especially — runs well past 60s, so Cloud Run hard-killed the request mid-stream and the response never reached the browser. Raised to 3600s (the Cloud Run service maximum) on both services; operator-reviewed + applied; verified live at 3600s.
  • Chat error-hardening (pw-os-v2 #378). streamErrorClientMessage() maps an Anthropic prompt-too-long 400 (oversized documents) and a request timeout to specific, advisor-actionable messages; every other error still collapses to the generic text so infra detail never reaches the browser (audit finding L2). CHAT_MAX_OUTPUT_TOKENS lowered 128000→32000 (the per-request override is unchanged). A model_context_window_exceeded stop-reason notice replaces a silent truncation. 1325 tests pass; merged + deployed green.
  • Verified non-findings. 1M context is native to Claude Opus 4.7 — no beta header (the context-1m-* beta was Sonnet-4-only); the chat already had the full window. The SSE parser was already correct — pw-os-v2 emits single-line JSON SSE events, so the multi-data:-line bug class (pw-portal-v2 #76) cannot occur here. Neither needed a change.

Next

P1 — the conservative-power adaptive request mode (pw-os-v2) — is queued for a fresh session. mail-auth Stream A (pw-api) + the posture/display super-prompt (pw-api→pw-os-v2) are queued. Memory: streaming-chat-failure-check-cloud-run-timeout-first.

2026-05-22 (CES implementation Phase 1 — pw-api substrate MERGED)

CES Implementation Phase 1 built + merged the pw-api-canonical foundation of the CCO-governed Controlled Exception System, per the ACCEPTED design ADR — landed on pw-api main via PR #266. No operational exception is activated; the foundation is inert against an empty cco_approved_exceptions table.

Estate-wide PRs (pw-api)

Repo PR Title
pw-api #266 feat(ces): CES Phase 1 — pw-api substrate (consolidated A–D) — MERGED

Phase 1 was authored as a 4-PR linear stack (#262–#265); a manual stacked-merge scrambled it (gh pr merge on a stacked PR merges into the feature-branch base, not main), so the complete, intact Phase 1 was consolidated into a single PR #266 and merged.

What advanced

  • PR A — migration 0056 creates cco_approved_exceptions (mutable, tenant-RLS ENABLE+FORCE) with CHECK constraints enforcing the CCO-ratified invariants (ssn/credit_card + bank_account_number/ein/itin never exception-eligible; single context; ai_destinations subset; document_workflowentity_hash) + a BEFORE INSERT trigger for the Q2 max-5-active-per-operator limit.
  • PR B — the pii.exception.* audit namespace (submitted / granted / rejected / invoked / revoked / expired).
  • PR CgetActiveExceptions(), a per-tenant in-process-cached lookup; fail-closed on a fetch error.
  • PR DassertEgressCanaryCleanWithExceptions(), the canary CES gate — a hit covered by an active exception is an audited pass-through (pii.exception.invoked); an uncovered hit still fires; ssn/credit_card are never consulted. The existing canary path is unchanged.
  • Engineering substrate — no operational exception activated, no compliance interpretation made. CI green (lint/typecheck/unit, migration dry-run, security scans); migration 0056 applies + deploys to the pw-api Cloud Run service on the merge.

Iteration notes

  • The iteration's earlier §6 worktree block (the pw-api tree was parked on a stale merged branch) was cleared by an operator pw-api re-anchor; the run then completed Phase 1. Block diagnostic: strategy/diagnostics/2026-05-22-ces-implementation-phase-1-worktree-blocked.md.
  • A first CI run surfaced a Postgres array_length-empty-array gotcha (an empty-array CHECK evaluated to NULL and passed) — fixed to cardinality() in two CHECK constraints.
  • A pre-existing broken pnpm-workspace.yaml on pw-api main (an allowBuilds block with no packages: field, from commit 9a4086a) had silently failed the npm audit required check for every pw-api PR — fixed in #266 (packages: ['.']).

Next

Phase 2 (pw-os-v2 / pw-portal-v2 BFF consultation + scan()-layer consultation) and Phase 3 (CCO management UI) follow as separate iterations; per-exception activation needs explicit Adam-CCO approval.

2026-05-22 (pwportal chat UX — markdown rendering + SSE whitespace fixes)

Two pre-existing pwportal client-chat rendering bugs are fixed — the AI Travel Assistant now renders Claude's markdown as formatted HTML, and streamed text no longer loses inter-chunk whitespace. Both were latent under Haiku 4.5's short outputs and surfaced once the client surface moved to Sonnet 4.6 (the chat-policy upgrade, pw-portal-v2 #74). Autonomous engineering-substrate session — no CCO perimeter.

Estate-wide PRs (2; pw-portal-v2)

Repo PR Title
pw-portal-v2 #75 fix(chat): render assistant markdown in the Travel Assistant chat
pw-portal-v2 #76 fix(chat): preserve whitespace across streamed SSE chunks

What advanced

  • Bug A — markdown rendering (PR #75). The chat message component rendered assistant content as a raw text node, so markdown showed as literal **, #, and | syntax. Assistant turns now render through react-markdown + remark-gfm into the hand-written .prose stylesheet index.css already carried; user turns stay plain text. GFM table styling added. @tailwindcss/typography was deliberately not added — the existing .prose class would have collided (substrate-evidence catch against the session brief).
  • Bug B — SSE streaming whitespace (PR #76). The chat SSE parser took only the first data: line of each frame and ran a blanket .trim() — adjacent text deltas concatenated ("walkable" + " it" → "walkableit") and any delta containing a newline lost content. Replaced with a spec-compliant parse: collect every data: line, strip exactly one framing space, rejoin with \n.
  • Test infrastructure. apps/web had no test runner; vitest 4.x was added (matching apps/api) with a CI step. 20 web tests cover both fixes.

Next

The pwportal chat UX overhaul iteration is closed; both PRs merged + CI green + deployed on merge-to-main. The CES implementation iteration remains estate priority 1.

2026-05-22 (CES — design ADR ACCEPTED on CCO substantive review)

The CES design ADR is promoted DRAFT → ACCEPTED — Adam-CCO completed the substantive review; the design phase is closed and the CES implementation iteration is unblocked.

Estate-wide PRs (pw-shared)

Repo PR Title
pw-shared #341 docs(adr): promote CES ADR DRAFT → ACCEPTED — Adam-CCO substantive review (merged)

What advanced

  • CES design ADR ACCEPTED — promoted via PR #341 (Adam-CCO approved + merged 2026-05-21). The seven-dimension design is cleared for implementation.
  • CCO substantive review complete — Adam-CCO ratified all five open questions with substantive additions on four: Q1 (extend the never-eligible list to bank_account_number/ein/itin/similar); Q2 (operator standing-exception operational limits — single context, entity_hash required for business-contract exceptions, max five active per operator); Q4 (use-it-or-lose-it — 90-day zero-invocation auto-revoke — + CCO anomaly alerts); Q5 (Dimension 5 UI additions — PDF audit-trail export, quarterly report, search, CCO email notifications). These additions are CCO-mandated requirements for the implementation iteration.
  • CCO-REVIEW books-and-records record — Adam-CCO's substantive review is recorded as a REVIEW entry in compliance/cco-approvals/ (PW-CONTROLLED-EXCEPTION-2026-05-21-ADR-REVIEW.md; operator committer-of-record), closing the ADR's promotion criterion 1.

Next

The CES implementation iteration is unblocked — cco_approved_exceptions table + migration, canary + scan() CES-consultation, pii.exception.* audit namespace, CCO management UI, the Tier 1 roster-tokenization build — operator-merge-gated, carrying the CCO additions above. Per-exception operational activation remains gated on explicit Adam-CCO approval.

2026-05-22 (CES — design ADR revised + books-and-records record committed)

The CES design-phase deliverables are complete pending the CCO's substantive review: the books-and-records authorization record is committed into compliance/cco-approvals/, and the design ADR DRAFT was revised to incorporate the CCO's actual verbal articulation. A continuation of the same-day CES design-phase kickoff (below).

Estate-wide PRs (pw-shared)

Repo PR Title
pw-shared #339 docs(cco-approvals): CES design-phase authorization record — operator-committed (Rule 17a-4 books-and-records)
pw-shared #335 docs(adr): CES design ADR DRAFT revised for the CCO's verbal articulation (open; operator-merge-gated)

What advanced

  • Books-and-records record COMMITTED at compliance/cco-approvals/PW-CONTROLLED-EXCEPTION-2026-05-21-APPROVAL.md — operator committer-of-record per CLAUDE.md §1.3 + Rule 17a-4 provenance. It anchors the CCO's verbal design-phase approval (2026-05-21 13:34 ET, Google Meet) + the follow-up verbal confirmation of corrected framing.
  • CCO verbal confirmation OBTAINED — the CCO verbally confirmed the corrected substantive framing in a follow-up call/chat: "purposeful intent" / "case-by-case basis" / "human oversight" as the governing principles; the dual approval pathway (CCO-direct or partner-with-CCO-approval); the dual AI destination (Anthropic ZDR and/or Gemma); approved data types names / addresses / phones / emails.
  • ADR PR #335 REVISED — the design ADR DRAFT now reflects the CCO's verbal articulation: partner submission pathway, dual AI destination, and gate-layer coherence (the exception spans the egress canary and the scan() / tokenization layer — verified from code: names/addresses are not canary hit types). The Context quote was corrected to separate the CCO's substantive verbal intent from the CTO/CISO's engineering synthesis ("scope tuple"). Six ADR open questions reduced to five — grant authority is resolved by the articulation. Status DRAFT; substantive CCO review gates ACCEPTED.
  • Substrate-evidence-discipline instance #17 RESOLVED — the meeting-notes corroboration gap (the auto-generated Google Meet summary did not capture the CES discussion) is closed: the operator's contemporaneous record + the CCO's follow-up verbal confirmation are the Rule 204-2 books-and-records substrate. The earlier plan to seek a written corroboration was satisfied by the verbal re-confirmation, captured contemporaneously.

Operator next-action

Forward the committed books-and-records record + the revised ADR PR #335 to the CCO for substantive ADR review. On the CCO's substantive review + resolution of the five open questions, the ADR promotes DRAFT → ACCEPTED; the CES implementation iteration is gated on that promotion.

2026-05-22 (CES controlled-exception-system — design-phase kickoff)

Design-phase kickoff of the CCO-governed Controlled Exception System (CES) — a scope-bounded, fully-audited exception mechanism over the PII egress canary. The CCO verbally authorized the design phase; two DRAFT deliverables, no code. After they landed, the operator identified four material content corrections — a revision pass is queued before the documents forward to the CCO for substantive review.

Estate-wide PRs (2; pw-shared)

Repo PR Title
pw-shared #335 docs(architecture): CES design ADR DRAFT for CCO review (open; operator-merge-gated)
pw-shared #336 docs(strategy): sequence the CES iteration — dispatch queue + briefing (merged)

Deliverables

  • CES design ADR DRAFT (architecture/decisions/ADR-cco-controlled-exception-system.md; PR #335) — 7 design dimensions; generalizes the canary's existing narrow b2b-counterparty classification override into CCO governance. Dimension 7 recommends consolidating the deferred Tier 1 (names + phones) auto-tokenization. Status DRAFT; promotion to ACCEPTED gated on CCO substantive review.
  • CES books-and-records approval record — staged at strategy/drafts/ for operator commit into compliance/cco-approvals/ (agent hard-stop path; the operator is committer-of-record per Rule 17a-4 provenance).

Revision queued

Four operator-identified corrections — the CCO's actual verbal articulation vs. operator architectural synthesis; a partner-with-CCO-approval pathway; dual Anthropic-ZDR / Gemma destination scope; a meeting-summary corroboration note — are scoped to a dedicated next-session revision pass. The corroboration note records, per substrate-evidence-discipline, that the auto-generated meeting summary did not capture the CES discussion; the operator seeks the CCO's written corroboration before the canonical books-and-records record commits (cross-system consistency under Rule 204-2).

2026-05-22 (chat model + max_tokens configuration policy — ADR + Phase 2A/2B)

The Track A tool-firing fix was expanded by operator directive into a chat model + token configuration policy across the chat surfaces. Three operator-locked decisions, an updated ADR, and two implementation PRs — all reviewable, operator-merge-gated, open for review.

Estate-wide PRs (3 across 3 repos)

Repo PR Title
pw-shared #332 docs(adr): PWOS chat model + max_tokens configuration policy (DRAFT)
pw-os-v2 #376 fix(chat): raise chat max_tokens to 128000 + surface truncated tool calls
pw-portal-v2 #74 fix(chat): default client chat to Sonnet 4.6 + raise max_tokens to 64000

The policy

  • Default model by surface. Advisor (pw-os-v2) → Opus 4.7 (already the default — no change). Client (pw-portal-v2) → Sonnet 4.6 (was Haiku). Chat-session naming (pw-api) → Haiku 4.5 (preserved; verified, no change).
  • Max output tokens by surface. Advisor CHAT_MAX_OUTPUT_TOKENS = 128000; client DEFAULT_MAX_TOKENS = 64000. max_tokens is a ceiling — not billed, and verified not to factor into OTPM rate limits.
  • Truncated-tool_use handling. A claude.tool_use_truncated structured log + an explicit user-facing notice replace the silent discard — closing the Track A Bug-2 silent failure.

Substrate-evidence-discipline

The directive locked 131072 / 65536 as "the model ceilings." The Anthropic docs confirm Opus 4.7 = "128k" / Sonnet 4.6 = "64k" max output (synchronous Messages API, no beta header) — but as "128k"/"64k", not exact integers. 128000 / 64000 were used instead — provably ≤ the cap under both the decimal and binary readings, so they cannot 400 every chat request, while fully achieving the policy intent. ADR: architecture/decisions/ADR-pwos-chat-max-tokens-fix.md.

2026-05-22 (substrate quality sprint — tool-firing investigation + Tier 1 bridge fixes)

A substrate quality sprint: a P0 tool-firing bug investigated to root cause, plus the first Tier 1 bridge fixes from the PWOS substrate self-audit. Track A pinned why PWOS chat write tools silently no-op; Track B shipped 2 of 7 audit bridge fixes and captured the rest with precise deferral reasons.

Estate-wide PRs (5 across 2 repos)

Repo PR Title
pw-shared #329 docs(diagnostics): PWOS tool-firing bug — root cause + fix spec
pw-shared #330 docs(diagnostics): tool-firing bug — production telemetry confirmation (§6.1)
pw-shared #331 docs: iteration close — substrate quality sprint
pw-os-v2 #374 fix(api): render Wealthbox linked_to objects instead of "[object Object]"
pw-os-v2 #375 fix(api): show "Shared Drive" as owner for Shared Drive search results

Track A — tool-firing bug (P0)

PWOS chat write tools that inline a large content payload (drive_create_doc's initial_content, etc.) silently no-op — the assistant says "creating it now" and nothing happens, no error. Root cause is a two-bug compound: routes/chat.ts never raises maxTokens above the claude-client.ts 4096 default, so a large tool_use truncates mid-emission (stop_reason: max_tokens); the tool loop then discards any tool_use on a non-tool_use stop reason. Three evidence layers converged — code trace, Cloud Logging event sequence, and an operator audit_log query (5 of 9 turns at exactly 4,096 output tokens). The same cap also truncates long plain-text responses. P0; no CCO perimeter. The fix is multi-file with design choices — captured as a next-iteration ADR-DRAFT-then-reviewable-PR item, not shipped. Record: strategy/diagnostics/2026-05-22-pwos-tool-firing-bug-investigation.md.

Track B — Tier 1 bridge fixes

The PWOS substrate self-audit (read-only, ~59 tools; strategy/diagnostics/2026-05-22-pwos-tool-surface-audit.md) surfaced seven bridge bugs. Two shipped: Wealthbox linked_to "[object Object]" (#374) and Drive-search Shared Drive owner "?" (#375). Five deferred with precise reasons: the UUID-scrubber fix is pw-api-canonical and pw-api is on an active feature branch; the Tradier / MBOUM / OpenCover field-mapping fixes live in pw-api and need live upstream API responses pulled first (some "broken" fields are likely sandbox-tier limits, not bugs). Separately, the Altruist OAuth callback (webhooks.protocolwealthllc.com/auth/altruist/callback) was surfaced broken in production — the route handler is unregistered after the pwdashboard retirement — and is captured as the lead pw-api-iteration item.

Substrate-evidence-discipline

Twelve instances across the 2026-05-19→22 arc. This sprint: the directive's example log query carried the wrong date and assumed non-existent log fields; a NEW hypothesis was disproved by tracing code; and the operator's audit_log query added a third converging evidence layer. Memory: 2026-05-22-evidence-layers-converge-on-mechanism, 2026-05-22-substrate-self-audit-pattern.

2026-05-22 (Tier 1 ADR-misread targeted fix — Bug A + Bug B; verified in production)

The Tier 1 auto-tokenization implementation iteration corrected its own ADR rather than building from it. A code-level verification before operator endorsement found the ADR's premise — that scan()'s vendor_tool_result context does not tokenize emails — was a misread of pii.ts: the cited line is from the maskPII() doc-comment (the outbound display masker), not scan() (the egress tokenizer). scan() does tokenize tool-result emails. The 19 production PII-egress-canary email blocks were two unrelated, code-confirmed bugs — both fixed, deployed, and verified clean against a post-deploy Wealthbox-lookup test chat.

Estate-wide PRs (5 across 3 repos)

Repo PR Title
pw-os-v2 #372 fix(api): scan tool error strings for PII before egress (Bug A)
pw-os-v2 #373 fix(api): align canary email allowlist with pii.ts (Bug B)
pw-portal-v2 #73 fix(api): align canary email allowlist with pii.ts (Bug B cross-repo sync)
pw-shared #326 docs: correct the Tier 1 auto-tokenization ADR misread + bridge docs
pw-shared #327 docs: post-merge close — canary verification + dispatch-queue/state sync

The two bugs

  • Bug A — tool error strings bypassed the PII scan. The tool-loop scan sat behind if (result.ok); a failed tool call's error string reached the egress canary raw. A scrubToolText helper now scans + tokenizes both successful output and error strings.
  • Bug B — the egress canary's email allowlist had drifted from pii.ts's (4 domains vs 14); vendor-domain emails scan() passes raw were false-positive-blocked by the canary. The canary allowlist is now a verbatim 14-domain mirror — copied, not imported, so the canary's independent-verifier design is preserved. Synced to pw-portal-v2; the pw-api canary copy is deferred (active feature branch).

Verification

A post-deploy Wealthbox-lookup test chat tokenized 3 client emails across 4 canary-clean turns with zero pii_egress_canary.blocked events — the scenario that produced the 19 baseline blocks, exercised post-deploy, did not block. Tier 1 roster tokenization re-scoped to client names + phones and deferred as follow-on. Diagnostic + verification records under strategy/diagnostics/; memory artifact tier-1-adr-premise-misread-pii-ts-2026-05-22.

2026-05-22 (substrate decisions + capability inventory)

A continuation session off the 2026-05-20 audit remediation produced the substrate-composition foundation. The full-substrate capability inventory mapped the estate across the three-tier (N / G / A) processing model; four load-bearing decisions were recorded; five ADR DRAFTs + two CCO/operator dispatch DRAFTs authored; the EISDIR /assets/* directory-path fix shipped (pw-os-v2 #371). shared/ PRs #318#325.

  • Decisions: cvxportfolio dropped (cvxpy direct, Apache 2.0); per-surface routing via shared library primitives; EMF Python-canonical + TypeScript conformance-tested; chat-attachment retention RESOLVED (Adam-CCO Option A). Financial-planning math: BUILD in-house on permissive OSS.
  • Records: strategy/2026-05-21-full-substrate-capability-inventory.md; five ADR DRAFTs under architecture/decisions/.

2026-05-20 (audit remediation executed — bundles B1/B2/B3/B5 shipped; 4 pw-os-v2 PRs)

The overnight-audit remediation (dispatched earlier 2026-05-20, entry below) executed orchestrator-direct — the operator selected in-thread execution over worker dispatch and over phase-decomposed Agent-tool subagents. All four engineering-scope bundles merged to pw-os-v2 main; CI + deploys green.

pw-os-v2 PRs (4)

PR Bundle Findings closed Merged
#367 B1 H1 — vendor-update SQL column-name allowlist 2026-05-20
#368 B2 H2 + M1 — Component 10 substitution hardening (pre-substitution canary pass + bounded map) 2026-05-20
#369 B3 H3 + H4 + M3 — PII egress-canary coverage (tool_result SSN, dot-separated SSN, NFKC) 2026-05-20
#370 B5 L1 + L2 + L3 + L5 — data-layer conversation ownership; SSE error-leak; post-decode size guard; full sha256 digest 2026-05-20

Findings closed: 4 HIGH + 2 MEDIUM + 4 LOW

All HIGH (H1–H4) + the bundled MEDIUM (M1, M3) + four LOW (L1/L2/L3/L5) remediated + deployed. api test count 1287 → 1310 across the bundles.

Two scope decisions

  • B3 H3 — SSN-only in the tool_result bucket. The audit bundled phone; scan()'s vendor_tool_result context deliberately lets tool-result phone numbers through (CRM lookups return them), so the canary covers tool-result SSNs only — firing on phones would hard-fail routine advisor chat for no real leak.
  • B3 H4 — the pii.ts change deferred. pii.ts is pw-api-canonical with a hard CI sync gate (Contract #3 §3.3); the dot-separator fix to its US_SSN regex is cross-repo work. The canary change alone closes H4's egress risk.

Remaining (authored, not executed)

M2 (chat.message.superseded immutable audit-log row; CCO-approved 2026-05-20), the canary cross-repo sync (pw-api + pw-portal-v2), and B6 (GCS re-fetch robustness; B5's L4 folded in) remain as authored dispatch packets — strategy/dispatches/ + the audit record strategy/diagnostics/2026-05-20-overnight-substrate-audit.md.

2026-05-20 (overnight substrate security audit + 2 pw-os-v2 production fixes + remediation dispatched; 4 PRs across 2 repos)

Single-instance orchestrator session. Two production fixes shipped + deployed to pw-os-v2; a read-only overnight substrate security audit conducted; the remediation authored as dispatch packets and merged to shared/.

Estate-wide PRs (4 across 2 repos)

Repo PR Title Merged
pw-os-v2 #365 fix(chat): Agent-memory context 500 — query after_state not a non-existent details column 2026-05-20
pw-os-v2 #366 fix(chat): enforce the per-conversation attachment-total cap 2026-05-20
pw-shared #314 docs(dispatch): overnight-audit remediation — engineering-scope dispatch packets 2026-05-20
pw-shared #315 docs(adr): M2 — chat.message.superseded immutable audit-log row (DRAFT) 2026-05-20

Production fixes (pw-os-v2; both deployed)

  • #365 — Agent-memory context HTTP 500. The Component 7 Week 3 Deliverable 2 endpoint queried a details column that does not exist on audit_log (the table carries before_state + after_state); every call 500'd, latent since the endpoint shipped. Fix: after_state AS details + the endpoint's first test suite (5 cases incl. a regression guard).
  • #366 — per-conversation attachment-total cap. MAX_TOTAL_ATTACHMENT_BYTES_PER_CONVERSATION (15MB) was documented but unenforced — a conversation could accumulate unbounded attachment bytes re-fetched from GCS + re-sent to the model every turn. Now rejected with 413 conversation_full; 6 new tests.

Overnight substrate security audit

Read-only audit of pw-os-v2 (PII pipeline; chat attach + Components 9/10; auth/RBAC/audit-trail/SQL) via 3 parallel read-only review agents + orchestrator diagnostic. Verdict: codebase fundamentally sound — OAuth trust root, route protection, RBAC, the egress canary's fail-closed behavior, and audit-write fail-closed posture all hold up. Findings: 2 HIGH, 3 MEDIUM, 5 LOW; no CRITICAL externally-exploitable finding. Canonical record: strategy/diagnostics/2026-05-20-overnight-substrate-audit.md.

Remediation dispatched (shared/ #314 + #315)

Seven remediation bundles authored: B1 (H1 vendor-update SQL column-name allowlist), B2 (H2+M1 Component 10 substitution hardening), B3 (H3+H4+M3 canary coverage), B5 (L1–L5 LOW findings), the cross-repo canary sync (pw-api + pw-portal-v2 fast-follow), B6 (GCS re-fetch robustness), and M2 (chat.message.superseded immutable audit-log row). B1/B2/B3/B5 + sync + B6 are engineering substrate — CTO/CISO standalone per cco-gate-scope-substrate-vs-client-facing-2026-05-19. M2 is the sole CCO-perimeter item (SEC Rule 17a-4 / 204-2); CCO-approved 2026-05-20. Execution restructured to orchestrator-coordinated Agent-tool subagents (phase-decomposed under the 600s watchdog) per operator directive; the fresh-CLI worker dispatch packets at dispatch/cli-w*/pending.md remain a valid fallback.

2026-05-19 (post-meeting cycle: positioning anchor codified + /agents 5th Tier-1 surface LIVE; 8 PRs across 2 repos in ~90 min)

Single-instance hybrid orchestrator session ~17:00Z → ~19:30Z (~90 min wall-clock). Per CLAUDE.md §2.0 hybrid orchestration — global state in-thread; 1 worker dispatch (cli3) for substantive pw-website page write per §2.0 Q5; orchestrator-direct on shared/ docs + memory + outcome capture + sanitization. cli3 completed in ~35 min vs 60-90 min estimate (mature inheritance from cli2's /how-we-work pattern; §17 chain depth 0). Anchored by external-advisor meeting outcome: positioning frame "Solution for training AI agents, not launching them" + three-tier memory architecture (per-client / per-advisor / per-firm) as Component 7 substrate target.

Estate-wide PRs (8 across 2 repos)

Repo PR Title Merged
pw-shared #286 docs(changelog): style policy — no personal names + high-level summary ~17:30Z
pw-shared #287 docs(strategy): external-advisor demo outcome + training-not-launching positioning memory ~17:45Z
pw-shared #288 docs(sanitize): expanded no-names policy across CHANGELOG + COMPLIANCE-CHANGELOG + strategy + memory ~18:10Z
pw-shared #289 docs(sanitize): work-focused outcome rename + drop industry-journalist framing ~18:25Z
pw-shared #290 dispatch(cli3): /agents substrate-for-training page — 5th Tier-1 surface (Week 1 of Component 7) ~18:46Z
pw-shared #291 docs(strategy): pwos.app/agents scope doc + next-iteration-briefing refresh ~18:50Z
pw-website #117 feat(agents): /agents substrate-for-training page (5th build-in-public Tier-1 surface) ~19:20Z
pw-shared #292 chore(dispatch): cli3 task completed; archived ~19:25Z

Build-in-public Tier-1 — 5th surface LIVE

  • https://protocolwealthllc.com/agents — substrate-for-training page anchoring the positioning frame surfaced in the external-advisor meeting. Documents the three-tier memory model (per-client / per-advisor / per-firm) + substrate primitives (PII tagging + audit-log principal chain + RBAC + ADR file index) + verification pathways. Aspirational client-agent framing (substrate enables, surface in build runway). Same disclaimer pattern as /how-we-work (top + bottom + zero personal names + zero performance/AUM/advisory-service refs). Marketing Rule §206(4)-1 bundle umbrella from earlier today's CCO approval covers this 5th surface; per-surface ratification at convenience per batched-routing pattern.

Strategic positioning anchor codified

External-advisor meeting surfaced the frame: "Solution for training AI agents, not launching them. Operating system for the RIA > client portal layer." The 4 Tier-1 surfaces shipped earlier today + the substrate posture they describe collapse into a single positioning frame. Anchored in shared/strategy/2026-05-19-build-in-public-tier1-launch-outcome.md. Three-tier memory architecture (per-client / per-advisor / per-firm; gstack-inspired persona-shared-memory pattern) maps to existing PW substrate primitives + becomes the Component 7 anchor. Anticipated industry article publication mid/late June 2026 = next external-attention moment; build-runway 4-6 weeks queued.

Component 7 scope queued (pwos.app/agents functional surface)

shared/strategy/pwos-agents-functional-surface-scope-2026-05-19.md — 4 decision picks pre-drafted for operator confirmation before week-3 implementation dispatch (data-model mapping; RBAC enforcement; v0.1 vs v1.0 split; chat-infra interaction; recommendations included). Week 2 = reference implementation in pwos-core/examples/rias-agent-substrate/ + ADRs + architecture diagrams. Weeks 3-6 = functional surface implementation; Marketing Rule clearance for client-facing UX (v1.0). BlockSkunk Phase 0 prep (operator 90-min Wed/Thu) queued as parallel work-front per blockskunk-phase-0-prep-parallel-work-front-2026-05-19 memory.

5 new memory artifacts (16 cumulative for 2026-05-19)

  1. training-ai-agents-not-launching-them-positioning-2026-05-19 (project) — strategic frame for agent-related scoping
  2. role-only-no-personal-names-policy-scope-extended-2026-05-19 (feedback) — no-names policy extended firm-wide; two-tier provenance framework (aggregate role-only operational; named substantive-obligation per Rule 17a-4 / 206(4)-7 / SEC ADV)
  3. pattern-convergence-cross-instance-validation-2026-05-19 (feedback) — convergent multi-instance strategic analysis is substrate-validation signal
  4. blockskunk-phase-0-prep-parallel-work-front-2026-05-19 (project) — parallel work-front with /agents Component 7 build; both run through anticipated article window
  5. Companion: pwos-agents-functional-surface-scope-2026-05-19.md scope doc — 4 decision picks for operator confirmation

Substrate milestones

  • 5th build-in-public Tier-1 surface LIVE — first surface anchored in strategic positioning frame surfaced from external review (vs derived from internal capability inventory)
  • worker-self-fix-cascades-past-archive-window memory validated empirically downstream — cli2's prior PR #113#114#115 chain made today's /agents PR ship §17 chain depth 0 (inheritance pattern proves out across worker handoff boundaries)
  • 16 memory artifacts codified in single day — substrate growth on operational + strategic + policy + technical dimensions
  • Two-tier provenance framework codified (aggregate role-only operational documentation; named substantive-obligation records per Rule 17a-4 / 206(4)-7 / SEC ADV)

Operator-cost calibration

cli3 shipped /agents in ~35 min wall-clock + ~3 min orchestrator-direct sync (~38 min total worker→PR→merge→archive). Pattern inheritance from cli2's /how-we-work (snapshot pattern + ambient *?raw types + disclaimer component reuse) reduced wall-clock ~50% from estimate. §17 chain depth 0 on PR #117. Orchestrator-direct shared/ PRs (286-292 = 7 PRs) shipped at ~12-min average via standing PR-merge authorization per orchestrator-pr-merge-standing-authorization memory.


2026-05-19 (build-in-public Tier-1 launch + Marketing Rule §206(4)-1 bundle approved + observability access grants; 12 PRs across 4 repos in ~75 min)

Single-instance hybrid orchestrator session 14:54Z → ~16:53Z (~120 min wall-clock). Per CLAUDE.md §2.0 hybrid orchestration — global state in-thread; 2 worker dispatches (cli1 + cli2) for substantive code-repo writes per §2.0 Q5; orchestrator-direct on shared/ docs + small README + CCO bundle stage. cli2 demonstrated cascading §17 SELF-FIX (PR #113#114 → #115) for the first time at N≥2 chain depth.

Estate-wide PRs (15 total: 12 in launch cycle + 3 in follow-on /how-we-work cycle)

Repo PR Title Merged
pw-shared #274 P1: COMPLIANCE-CHANGELOG entry + 5-fact-sheet kit (pandoc/xelatex; 5 MD + 5 PDF + 3 SVG + LaTeX template) 15:07Z
pw-shared #275 P2: cli1 + cli2 worker dispatches + Marketing Rule bundle stage 15:15Z
pwos-core #36 README advisor section + 17-row package-to-regulation map + Apache 2.0 rationale 15:18Z
pw-shared #276 Marketing Rule §206(4)-1 bundle APPROVED (all 6 items; record at cco-approvals/PW-MARKETING-RULE-BUNDLE-2026-05-19-APPROVAL.md) 15:28Z
pw-website #113 feat(build-in-public): /changelog + /factsheets Tier-1 surfaces 15:35Z
pw-shared #277 Langfuse ADMIN access-control-register entry (CCO + CIO observability access) 15:39Z
pw-os-v2 #360 feat(live): pwos.app/live engineering substrate transparency dashboard (1188 LOC; 16 new tests) 15:40Z
pw-website #114 fix-forward 1: snapshot reorg (commit src/content/ snapshots; CI self-contained) 15:40Z
pw-website #115 fix-forward 2: ambient *?raw module types in env.d.ts (decouples tsc from astro state) 15:43Z
pw-shared #278 cli2 archive (pw-website Tier-1 surfaces completed) 15:49Z
pw-shared #279 cli1 archive (pwos.app/live dashboard completed) ~15:55Z
pw-shared #280 Launch-cycle rollup (CHANGELOG + CURRENT-STATE + memory codifications) ~16:10Z
pw-shared #281 dispatch(cli2): /how-we-work 4th-surface dispatch authoring 16:24Z
pw-shared #282 docs(how-we-work): 3 content edits + external-advisor-quote section pre-launch 16:38Z
pw-website #116 feat(how-we-work): plain-English substrate page LIVE (cli2; ~50 min wall-clock; §17 chain depth 0) 16:48Z
pw-shared #285 cli2 archive (/how-we-work completed) 16:53Z

Build-in-public Tier-1 LIVE — 4 public surfaces

  • https://pwos.app/live — engineering substrate transparency dashboard. Substrate health table + Recent landings (top-10 from sanitized shared/CHANGELOG.md) + Verification pathways + Calendly CTA + persistent non-dismissible engineering-transparency disclaimer banner. BFF route + Hono GitHub-API cache (5-min TTL in-memory) + bake-at-PR-author-time static config. Data-source allow-list HARD-enforced.
  • https://protocolwealthllc.com/changelog — sanitized server-rendered cross-repo CHANGELOG. Astro build-time generation; consumer-local snapshot at pw-website/src/content/CHANGELOG.md refreshed via scripts/sync-shared-content.mjs on prebuild; CI builds against committed snapshot. Public-repo PR refs linked; internal-repo refs text-only. Defensive client-identifier sanitizer aborts build on hit.
  • https://protocolwealthllc.com/factsheets — index card grid + 5 per-slug HTML pages (pwos-at-a-glance, advisor-ai-vendor-audit-checklist, oss-ria-tooling-reading-list, vendor-ecosystem-reference, os-licensing-partner-faq) + 5 downloadable PDFs (50-55KB each).
  • https://protocolwealthllc.com/how-we-work — plain-English philosophical anchor; 4th and final Tier-1 surface. Sections: substrate-vs-vendor framing; why build in public; "shape of what's emerging in advice" (5 quotes from external advisor on AI scandal / coaches with expertise / performance statement of future / not a "channel" / visibly altruistic attracts clients) + 6 conviction paragraphs (open source = no surprise vendor pressure; practitioner-and-builder; retainer/fee-for-service default; treat people differently to treat them the same; profitability through community; willingness to be wrong on the record).

Marketing Rule §206(4)-1 bundle APPROVED

6-item bundle cleared substantive content review (~10-min turnaround via Google Chat; faster than 33-min canonical baseline). Canonical record at shared/compliance/cco-approvals/PW-MARKETING-RULE-BUNDLE-2026-05-19-APPROVAL.md. Items: posture-doc broader distribution + 5-fact-sheet kit /factsheets distribution + /live substrate-transparency posture ratified + /changelog substrate-transparency posture ratified + /factsheets all 5 approved + PII canary operational-completeness posture-doc revision authorized. External announcement channels (newsletter, blog, partner outreach, conference materials) UNBLOCKED. Pattern: CTO/CISO pre-framing of substrate-transparency-vs-commercial-communication classification accelerates CCO decision velocity ~3x on substrate-transparency batches.

Langfuse ADMIN observability access grants

CCO + CIO promoted to ADMIN at Langfuse org "Protocol Wealth LLC" via Settings → Members admin UI (operator-side; no code-repo write). Trigger: unauthorized dashboard.chart error post-SSO-auto-provision (v2 default = VIEWER/NONE; dashboard.chart scope requires MEMBER+). Compensating controls retained: Google OAuth SSO enforcement + username/password disabled + account-linking disabled + Cloud Run INTERNAL_ONLY ingress + OWNER role NOT granted (peer-admin reserved to CTO/CISO). Books-and-records at shared/docs/compliance/access-control-register.md per Rule 17a-4 + 206(4)-7.

§17 SELF-FIX validated at N≥2 chain depth

cli2 cascading fix-forwards in 8 minutes without orchestrator intervention: PR #113 initial → main Tests failed post-merge (sibling shared/ checkout missing) → PR #114 snapshot reorg → main Tests failed again (.astro/types.d.ts not generated pre-tsc) → PR #115 ambient *?raw declarations → main GREEN at sha c7efb8a. cli1 also caught 3 architecture divergences from dispatch spec (apps/api/ not apps/bff/; TanStack declarative routes; bake-at-PR-author-time) — translated all 3 as §0.2 implementation patterns WITHOUT Phase 1.5 STOP (none of the explicit triggers fired). Validates Phase-1.5-vs-§17 boundary: premise-invalidating constraints → Phase 1.5; implementation translation → §17 self-fix.

3 new memory artifacts (5 total today including 2 cli2-surfaced; 11 cumulative for 2026-05-19)

  1. worker-self-fix-cascades-past-archive-window (feedback) — §17 BOUNDARY scales past first-fix; workers poll main push-event CI AFTER archive + fix-forward without orchestrator unblock
  2. tsc-noemit-pre-astro-needs-ambient-raw-types (reference) — Astro/Vite CI needs ambient *?raw in src/env.d.ts; 15-line fix; zero runtime impact
  3. cross-repo-raw-imports-need-snapshot-not-sibling-checkout (feedback) — snapshot consumer-local + prebuild sync; CI fully reproducible without sibling checkout
  4. changelog-style-no-personal-names-high-level-summary (feedback) — CHANGELOG style: no personal names; refer to roles; high-level summary; formal sign-off provenance lives in cco-approvals/ where names + signatures are required for audit

Substrate milestones

  • First build-in-public production launch (4 public engineering-transparency surfaces; books-and-records under Rule 17a-4 via git-history + GCS WORM substrate)
  • First Marketing Rule §206(4)-1 batched approval at <15-min turnaround (CTO/CISO pre-framing pattern validated)
  • First §17 SELF-FIX BOUNDARY validation at N≥2 chain depth (cli2's 3-PR chain in 8 minutes)
  • First observability-access grants under Rule 17a-4 + 206(4)-7 access-control discipline (Langfuse ADMIN to CCO + CIO)
  • First CHANGELOG style policy codified (no personal names; high-level summary; cco-approvals/ holds named provenance)

Operator-cost calibration

15 PRs in ~120 min wall-clock with ~10 operator coordination messages. cli2's 4th-surface (/how-we-work) shipped in ~50 min vs 80-95 estimated (35-45 min under). §17 SELF-FIX cascading worked without orchestrator intervention on PR #113#114#115 chain; PR #116 (post-launch) shipped with §17 chain depth 0 (cli2's prior chain made today's fixes durable).


2026-05-19 (mid-day diagnostic follow-up cycle — 6 of 12 TODOs resolved; 4 PRs across 4 repos; 3 new memory artifacts; 17 worktree orphans cleaned)

Single-instance hybrid orchestrator session 12:47Z → 14:50Z (~2h wall-clock). Per CLAUDE.md §2.0 hybrid orchestration filter — global state held in-thread; no worker dispatches; no subagent spawns; all PRs orchestrator-direct. Resolves the highest-impact P1 + P2 TODOs from the overnight diagnostic (shared/strategy/2026-05-19-overnight-diagnostic-followup-todos.md).

Estate-wide PRs (4 across 4 repos)

Repo PR Title Merged
pwos-core #35 fix(ts): unblock Release build under @types/node 25 (6-package tsconfig override) 13:07Z
pw-api #250 Build(deps-dev): Bump brace-expansion 5.0.5→5.0.6 (dependabot patch; recreate cycle) 13:16Z
pw-infrastructure #198 fix(audit-logs): match gcloud-locked retention_period 220903200 = 365.25d×7y 14:31Z
nexus-core #28 deps: update redis >=5.2.0 → >=7.4.0 (floor bump; zero call sites verified) 14:49Z

Plus: pw-api #257 PII canary 3rd copy merged 12:31Z (re-run cycle initiated by cli1 pre-session per TODO 1); pw-api #252 CLOSED by dependabot ("deps updatable in another way") → replacement group pw-api #258 opened with 12 updates (auto-merge ON; pending CI).

Root cause refinements (diagnostic hypothesis → actual)

  • pwos-core Release — diagnostic hypothesized "revert PR #33 @types/node 22→25"; investigation showed Release broke 2026-05-15T11:56Z when PR #27 first bumped @types/node 22.19.17 → 25.8.0. v25 dropped TextEncoder/setTimeout globals + tightened dynamic await import("node:*") resolution. Fix is per-package tsconfig override (not version revert) — affects 6 packages with dual browser-fallback/Node-fallback code paths.
  • pw-infrastructure TF audit-logs apply — diagnostic hypothesized "post-apply notify step"; actual = TF spec retention_period = 220752000 (7×365 days naive math) vs live LOCKED 220903200 (7×365.25 days from gcloud --lock-retention-period leap-year averaging). Every apply tried to reduce the immutable lock by ~1.75 days. Fix = bump TF value to match live (no behavioral change; terraform plan returns no-op post-fix).

3 new memory artifacts codified (operator-approved)

  1. gcs-lock-retention-period-365-25d-math (reference) — gcloud --lock-retention-period uses 365.25-day averaging; TF spec MUST match live to the second. Affects future immutable GCS WORM buckets (signed_document_archive next).
  2. dependabot-rebase-vs-recreate-after-auto-merge (reference) — @dependabot rebase refuses after auto-merge enable; @dependabot recreate works (may close+replace PR; re-enable auto-merge on replacement).
  3. types-node-v25-removed-global-textencoder (reference) — v25 removed globals; per-package tsconfig fix pattern; do NOT set at tsconfig.base.json if some pkgs lack @types/node (TS2688).

Worktree cleanup (TODO 6 ✅)

17 orphan worktrees removed across pw-api (4) + pw-os-v2 (10) + pw-portal-v2 (2) + pw-infrastructure (1). Each branch cross-referenced against gh pr list --state all before remove per orphan-cleanup-must-exclude-open-pr-branches memory. shared/ subagent worktrees (11; locked; .claude/-scoped) DEFERRED per diagnostic doc "not urgent" + isolation.

Diagnostic follow-up status

6 of 12 TODOs resolved (1, 3, 4, 5, 6, 12). Open: TODO 7 (ADV cron first-fire verify; 2026-05-20T03:30Z natural fire window); TODO 8 (audit_log Haiku cost telemetry SQL; operator-authorized); TODO 9 (pw-website dependabot majors; operator-deferred per eslint v10 prereq); TODO 10 (ROADMAP path annotations; deferred); TODO 11 (emf-canonical/terminology-map re-verify; deferred); TODO 2 superseded by TODO 3 resolution.

Operator-cost calibration

~2h wall-clock for 6 TODOs resolved + 4 estate-wide PRs + 3 memory artifacts + 17 worktree orphans cleared with ~3-4 coordination messages. Validates §2.0 hybrid orchestration filter at sustained mid-day intensity. No worker dispatches needed; single-instance held the cross-repo state cleanly across pw-api + pwos-core + pw-infrastructure + nexus-core + shared/ surfaces.


2026-05-19 (post-cascade additions — overnight diagnostic + CLAUDE.md §0 + §2.0 + 12 follow-up TODOs; 3 PRs)

Three orchestrator-direct PRs landed post-cascade-close 04:17Z–11:21Z 2026-05-19 in pw-shared. Read-only overnight diagnostic worker (cli1 shape) followed by post-merge investigation of 2 surfaced priorities + canonical CLAUDE.md additions codifying this iteration's hybrid-orchestration learnings.

pw-shared (3 PRs)

PR Title Stream
#267 docs(diagnostics): overnight diagnostic report — 2026-05-19T04:02:37Z (606 lines; 9 canonical checks + 7 iter-specific additions; 1 HIGH browser-resolved + 5 MEDIUM + 12 INFO + 4 LOW) cli1 read-only diagnostic worker (single-instance hybrid; no subagent spawns; 71 PRs cross-correlated across 8 repos)
#268 docs(strategy): overnight diagnostic follow-up TODOs — investigates P2 + P3 (pwos-core Release root cause = @types/node 22→25 NOT actions/checkout v6; PR #257 PII canary blocker = pre-existing flake clients-merge.spec.ts:368 409→200) orchestrator-direct investigation + 313-line TODO doc with P1–P4 priority tiers
#269 docs(claude-md): add §0 coding posture + §2.0 hybrid orchestration (1M context era) orchestrator-direct CLAUDE.md canonical extension

CLAUDE.md canonical extensions

  • §0 Coding posture (NEW; before §1): four-guideline upstream filter every agent runs before any code-affecting action. 0.1 think before coding; 0.2 simplicity first; 0.3 surgical changes; 0.4 goal-driven execution. Each carries a PW corollary (CCO scope pre-bounded; compliance-critical exempt; 83f346b incident; §8 three-strikes prerequisite).
  • §2.0 Hybrid orchestration (NEW; inside §2): 4-question filter applied BEFORE the §2.1–§2.8 spawn triggers fire. Hybrid pattern — orchestrator parent holds global state in-thread; subagents for audit-style (§2.1/2.2/2.3/2.6/2.7/2.8) + mechanical (§2.5). §2.4 refactor planning increasingly in-thread. UNCHANGED: §1.3 hard stops, §6 single-agent-per-repo, §8 three-strikes, §7 multi-agent review.
  • §11 step 1 patch: "§0 coding posture FIRST, then §1–§13 coordination" — flags the new operational layer.
  • prompts/orchestrator/daily-start.md companion: cold-start read order references §0 + §2.0; identity block adds hybrid-orchestration default.

Diagnostic substantive findings (full report at shared/strategy/diagnostics/overnight-diagnostic-2026-05-19.md)

  • /security HTTP 403: Cloudflare bot protection blocks curl + WebFetch on HTML routes; real browsers fine (operator-verified post-merge). PDF unaffected. Future overnight diagnostics use /browse skill (real headless Chrome) for live-surface health.
  • pwos-core Release 3× consecutive failure on main: root cause = @types/node 22.19.19 → 25.8.0 (PR #33) breaking node:crypto + TextEncoder type defs; actions/checkout@v6 (PR #32) is fine. Fix: revert PR #33 + pin to ^22.x (recommended) OR migrate tsconfig.
  • pw-api #257 PII canary 3rd copy: blocked solely on Migration dry-run integration test failing clients-merge.spec.ts:368 with AssertionError: expected 409 to be 200 — the known pre-existing pw-api main CI flake. Auto-merge already enabled (squash). Re-run CI to land.
  • pw-infrastructure Terraform push-event failures: 3 consecutive on main despite runtime applies succeeding (ZDR env live; ADV cron ENABLED; Anvil secrets queryable). Post-apply step is the failing actor.
  • pw-api-signed-documents-annual-adv-re-delivery-scan Cloud Scheduler: ENABLED + state.code=-1 + empty lastAttemptTime; first fire expected 03:30Z 2026-05-20.
  • 71 PRs landed clean across 8 repos in 24h window (overnight cascade + post-close additions); no production runtime errors in 24h Cloud Logging window.

Follow-up TODO inventory (12 items; full doc at shared/strategy/2026-05-19-overnight-diagnostic-followup-todos.md)

  • P1 pre-demo (by 11 AM ET 2026-05-19): 2 items — re-run PR #257 CI; demote pwos-core Release severity (not demo-blocking)
  • P2 fix-after-demo (EOD): 4 items — pwos-core Release fix; pw-infra Terraform post-apply audit; repo sync (pw-api + pw-infrastructure on stale branches); manual worktree cleanup (11+ orphans)
  • P3 verify-by-2026-05-23: 2 items — ADV cron first-fire verify; Haiku cost telemetry SQL
  • P4 operator-deferred: 4 items — dependabot batch; ROADMAP annotations; doc re-verify (emf-canonical.md + terminology-map.md lastVerified 2026-04-05); 5 memory artifact candidates

2026-05-19 (Component 4 e-signature + chat-naming + design-system v1.0 + Anvil terraform — full overnight cascade; 33+ PRs across 5 repos in ~6 hours)

Overnight cascade 2026-05-18 evening → 2026-05-19 ~00:30Z; ~6 hours wall-clock end-to-end; 33+ PRs landed across pw-shared + pw-api + pw-os-v2 + pw-portal-v2 + pw-infrastructure. Component 4 e-signature pipeline FULLY LANDED end-to-end (substrate + state-machine + AnvilClient + webhook + advisor surface + client signing iframe + ADRs + policy + runbook). Chat session naming live (cli5 sidebar pivot; first production pw-api Anthropic SDK egress via Haiku-4-5). Design-system v1.0 canonical (pwos.app + pwportal.app warm-light parity). Anvil terraform secrets + IAM + pw-api-env wired. CCO + CIO 46-pick REVIEW PR #242 (24 CCO + 10 CIO + joint EMF.8 + 13 operator-only) merged but responses pending async; Component 4 + chat-naming implementation proceeded with recommendations baked in as v1 canonical assumptions per soft-gate.

pw-shared (13 PRs)

PR Title Stream
#242 docs(cco-approvals): 46-point batched CCO + CIO REVIEW — Component 4 + 5 + dashboard + EMF/PWAF + chat-naming + chat-perf orchestrator-direct CCO+CIO REVIEW
#243 chore(dispatch): Component 4 e-signature 4-stream dispatch pending.md files orchestrator-direct dispatch
#244 docs(design-system): v1.0 — unified PW design + UX system (pwos.app + pwportal.app warm-light parity) design subagent
#245 chore(dispatch): integrate design-system v1.0 into Component 4 cli2 + cli3 pending.md orchestrator-direct dispatch update
#246 docs(component-4-e-signature): 2 ADRs + e-signature-policy + cadence runbook + Anvil vendor map + DD placeholder cli-shared (6-doc bundle, 2677 LOC)
#247 chore(dispatch): cli-shared Component 4 e-signature 6-doc bundle completed; archived orchestrator-direct archive
#248 chore(dispatch): chat session naming 2-stream dispatch (cli4 pw-api + cli5 pw-os-v2) orchestrator-direct dispatch
#249 chore(dispatch): cli1 Component 4 e-signature pw-api 3-PR sprint completed; archived orchestrator-direct archive
#250 chore(dispatch): cli3 Component 4 e-signature pw-portal-v2 client signing completed; archived orchestrator-direct archive
#251 chore(dispatch): cli2 Component 4 e-signature pw-os-v2 advisor surface completed; archived orchestrator-direct archive
#252 chore(dispatch): fix-forward cli3 completion report missing from PR #250 archive commit orchestrator-direct fix-forward
#253 chore(dispatch): archive cli5 chat-naming pending.md after PR #353 merge orchestrator-direct archive
#254 chore(dispatch): fix-forward cli5 completion report missing from PR #253 archive commit orchestrator-direct fix-forward
#255 chore(dispatch): archive cli4 chat-session-naming substrate (pw-api #255 merged) orchestrator-direct archive

pw-api (4 PRs)

  • #251 PR-C4-A — signed_document_archive substrate + 8 canonical actions (3 substrate signed_document.envelope.* + 3 lifecycle onboarding.signing.* + 2 sentinel retry pair) + sentinel-row reconciliation day-one + PII_TAGS day-one + BEFORE-UPDATE immutability trigger + 7-year WORM retention via GCS.
  • #253 PR-C4-B — AnvilClient + webhook handler (2nd production consumer of Track B' webhook-receiver primitive; Veriff is 1st) + 4-doc envelope state-machine service (IAA + Form ADV 2A + Privacy Notice + IPS).
  • #254 PR-C4-C — declined/expired HITL handlers + annual ADV cron (Rule 204-3 annual re-delivery cron + material-change re-execution flow) + WORM mirror + PDF/A-2b archival format.
  • #255 chat_sessions substrate — AUGMENT-shape parallel naming + audit metadata layer (chat naming v1). First production pw-api Anthropic SDK egress via Haiku-4-5 — pw-api joins pw-os-v2 + pw-portal-v2 as Anthropic SDK consumer.

pw-os-v2 (3 PRs)

  • #351 — cli2 advisor signed-documents tab + counter-sign + ADV 2A dashboard + §17 PII_TAGS port (byte-equal vendored from pw-api canonical).
  • #352 — design-system warm-light surface pass (pwos.app aligned with design-system v1.0 canonical).
  • #353 — cli5 chat sidebar pivot (chat-session-naming UI surface).

pw-portal-v2 (1 PR)

  • #68 — cli3 client signing iframe + 9-state machine + remediation flow (Anvil iframe embed; client-facing 4-doc envelope signing).

pw-infrastructure (3 PRs)

  • #193 — Anvil terraform secrets + IAM (ANVIL_API_KEY + ANVIL_WEBHOOK_HMAC_KEY secrets; Cloud Run SA permissions).
  • #194 — Anvil pw-api-env wiring (env block on pw_api Cloud Run resource).
  • #195 — ANTHROPIC_ZDR_CONFIRMED restore (pw-api needs the env var now that it makes Anthropic calls via chat-naming).

Substrate milestones earned this cascade

  • 3rd dual-canonical-action namespace consumer (substrate *.session.* + lifecycle onboarding.*.* pattern): KYC + risk-tolerance + signing
  • 3rd sentinel-row reconciliation consumer (per ADR-gcs-worm-audit-mirror R3): audit_log live + kyc_verifications + signed_document day-one
  • 2nd Track B' webhook-receiver primitive consumer (Veriff 1st; Anvil 2nd)
  • 1st production Anthropic SDK egress at pw-api (chat-session-naming Haiku-4-5)

Operator action items carry-forward (cli1 PR-C4-C report)

  1. Cloud Scheduler tick — daily 03:30 UTC POST to /v1/internal/cron/signed-documents-annual-adv-re-delivery-scan/run-next (terraform wire-up dispatch candidate)
  2. GCS bucket Object Lifecycle Lock verify on gs://pwllc-audit-archive (already locked 2026-05-02; confirm signed_document writes inherit)
  3. ANVIL_API_URL sandbox→production toggle after CCO C4.6 ESIGN/UETA confirmation
  4. Dogfood validation — 2 test clients end-to-end through 4-doc envelope

Gap surfaced for next-iteration dispatch

pw-api missing /v1/internal/signed-documents/ HTTP routes* (cli2 callout). cli1's PR-C4-C added lib layer; BFFs return 502 until follow-up adds advisor-facing HTTP routes; same posture as KYC + risk-tolerance from Components 2 + 3 — candidate Tier 0 next-iteration work.

CCO + CIO REVIEW PR #242 — responses async

REVIEW PR merged but CCO + CIO responses pending async at iteration close. Component 4 + chat-naming implementation work proceeded with recommendations baked in as v1 canonical assumptions per soft-gate. 24 CCO picks + 10 CIO picks + joint EMF.8 + 13 operator-only. Responses to be captured via companion APPROVAL doc per canonical batched-routing pattern when received.

Codification candidates queued for next iteration close (parallel subagent authoring)

  1. Append completion report BEFORE git mv — cli3 PR #252 + cli5 PR #254 fix-forwards; 3rd validation (prior precedent cli-shared + cli1 from Component 3 iteration); ready for ritual #19
  2. gh search vs --head for branch-name patterns with / — cli3 broken poll on chat-naming PR surfaced
  3. Workers re-anchor on origin/main before archive — carry-forward from Component 3 iteration
  4. Edit-before-git-mv pattern — carry-forward from Component 3 iteration
  5. Raw URL gh api 404 on private repos — carry-forward from prior iteration
  6. Archive PR-route default per ritual #15 — carry-forward from prior iteration
  7. Simple gh-poll-suffices for substrate gates — carry-forward from prior iteration

Operator-cost calibration

~6 hours wall-clock for 33+ PRs across 5 repos via 4-stream Component 4 cascade + parallel chat-naming dispatch + design-system bundle + Anvil terraform sequencing. Sustained velocity-per-message remained at ~1 surface per coordination touchpoint. Substrate validated end-to-end on first multi-stream Component-class implementation.


2026-05-18 (Component 3 risk-tolerance sprint — 12 PRs + CCO 17-pick batched routing approved + Component 5 scope landed + 6 new memory anchors)

Sprint window ~19:00–20:45Z UTC; ~1h 45min wall-clock end-to-end; 12 PRs landed across pw-shared + pw-api + pw-os-v2 + pw-portal-v2. Component 3 risk-tolerance verification substrate substantively complete (risk_tolerance_sessions + risk_tolerance_responses + risk_tolerance_question_bank + scoring engine + advisor review surface + 7 client assessment screens + ADRs + runbook + compliance policy). CCO approved all 17 batched picks (Component 2 carry-forward + Component 3 first-routing) via PR #227 comment in ~33 min turnaround — validates canonical batched-routing pattern. Component 5 custodian-data scope authored via 3rd START-IMMEDIATELY-validated Agent subagent.

Substantive PRs by stream

PR Repo Title Stream Merged LOC
#229 pw-shared docs(component-3-risk-tolerance): ADRs + compliance policy + runbook + vendor DD placeholders cli-shared 19:44:05Z 1099 LOC across 6 artifacts
#247 pw-api feat(risk-tolerance): substrate + 8 canonical actions + PII_TAGS + sentinel-row reconciliation (PR-RT-A) cli1 Task 1 19:59:55Z +1913/-1
#350 pw-os-v2 feat(risk-tolerance): advisor risk-profile detail view + bucket-override surface + needs_review escalation cli2 20:13:02Z 995 insertions
#248 pw-api feat(risk-tolerance): scoring engine + cadence + advisor override (PR-RT-B) cli1 Task 2 20:13:33Z 43 new tests
#67 pw-portal-v2 feat(risk-tolerance): 7 client assessment screens + Veriff-style state machine + BFF routes + PII_TAGS port cli3 20:22:54Z +2357 (17 files)
#228 pw-shared docs(component-5-custodian-data): scope doc DRAFT Agent subagent (3rd START-IMMEDIATELY validation) 19:35:04Z 406 lines
#249 pw-api feat(risk-tolerance): needs_review + integration tests + dogfood + client_assessments bridge (PR-RT-C) cli1 Task 3 20:40:57Z 11 unit + 5 integration tests
#227 pw-shared docs(cco-approvals): Component 2 carry-forward + Component 3 first-routing — 17-point batched CCO sign-off orchestrator-direct CCO REVIEW 19:52:04Z 342 lines
#233 pw-shared docs(cco-approvals): Component 2-and-3 carry-forward 17-pick CCO approval record — CCO approved orchestrator-direct CCO APPROVAL 20:38:35Z 142 lines

Dispatch + orchestration PRs (pw-shared)

PR #226 (4-stream dispatch pending.md) + #230 (cli-shared archive) + #231 (cli2 archive) + #232 (cli3 archive) + #234 (cli1 archive) + #235 (cli1 content-fix follow-up — validates edit-before-git-mv codification candidate).

CCO 17-pick batched routing — validated

  • PR #227 routing: orchestrator-direct CCO REVIEW with 9 Component 2 carry-forward + 8 Component 3 first-routing picks; cc-tag at 19:55:34Z
  • CCO response: bare "approved" PR comment at 2026-05-18T20:26:06Z UTC — ~33 minutes from cc-tag
  • APPROVAL doc filed: PR #233 (142 lines) — canonical Rule 204-2(a)(7) sign-off record
  • Pattern validates: ~10x operator efficiency at batched scale vs per-pick routing; canonical for future Component 4 + 5 picks (memory adam-cco-batched-routing-canonical)

Component 3 capabilities now live on main

  • risk_tolerance_sessions + risk_tolerance_responses + risk_tolerance_question_bank substrate at pw-api; migration 0051 + 0052 (client_assessments bridge); BEFORE-UPDATE immutability trigger; sentinel-row reconciliation day-one per ADR-gcs-worm R3
  • 8 canonical PW_ACTIONS at pw-api/src/lib/audit-actions.ts (3 substrate risk_tolerance.session.* + 3 lifecycle onboarding.risk_tolerance.* + 2 sentinel retry pair)
  • PII_TAGS.risk_tolerance_* canonical at pw-api; byte-equal vendored in pw-os-v2 + pw-portal-v2
  • Deterministic scoring engine at pw-api/src/lib/risk-tolerance-scoring.ts — MIT/Grable FRTS-13 normalize 1-70 + PW overlay 0-30 + composite 1-100 + bucket 1-33/34-66/67-100; golden-fixture test
  • Re-assessment cadence cron at Cloud Scheduler daily 03:00 UTC (3-year expiration + material-change + AUM threshold triggers)
  • Advisor override surface — assigned-CFP + CCO can override; cross-bucket leaps require CCO co-sign
  • needs_review→review_item flow at pw-api/src/lib/risk-tolerance-needs-review.ts — auto-creates governance review_item per ADR-review-items-primitive
  • Advisor /clients/:id risk-profile detail view + bucket-override modal + sentinel-row chain visualization (pw-os-v2)
  • 7 client assessment screens + 9-state client-side state machine + scored-pending polling + re-assessment invitation flow (pw-portal-v2)
  • ADRs + compliance policy + runbook at pw-shared (cli-shared #229)
  • CCO REVIEW + APPROVAL records at compliance/cco-approvals/ — Rule 204-2(a)(7) canonical sign-off

Sprint metrics + §17 SELF-FIX BOUNDARY validations

  • 1h 39min cli1 wall-clock for 3 sequential PRs (#247 + #248 + #249) — new sprint-velocity record on sequential-tasks-by-single-worker
  • 143 new tests in pw-api (1229 → 1372)
  • 10 Phase 1.5 picks via AskUserQuestion across cli1 Tasks 1+2+3
  • clients-merge.spec.ts:368 flake hit 2 of 3 PRs; both rerun-recovered cleanly (admin-override-aware Phase 6 100% success rate)
  • §17 SELF-FIX BOUNDARY validations:
    • cli1 caught + fixed objection_window_seconds=72h for review_items negation_default constraint violation (Task 3 self-discovery via own integration test)
    • cli1 caught + fixed assessment_type='risk_profile' enum validation (same)
    • cli1 surfaced latent aml-review-item.ts bug (flagged for separate fix; correct §17 boundary held)
  • Edit-before-git-mv pitfall validated TWICE this iteration — cli-shared #230 + cli1 PR #234/#235; both required follow-up commit. Strong codification signal for next iteration close.

Six new memory anchors codified (this iteration end-to-end including earlier-day work)

  1. aml-vendor-canonical-scorechain (canonical state anchor; Chainalysis moved off 2026-05-01)
  2. workers-re-anchor-origin-main-before-archive (codification candidate for ritual #19 from cli2 PR #218 squash-include)
  3. subagent-watchdog-prefer-fresh-cli-for-substantive-dispatch (subagent fit pattern; START-IMMEDIATELY mitigation parent memory)
  4. orchestrator-pr-merge-standing-authorization (operator durable instruction; orchestrator merges on green CI)
  5. NEW adam-cco-batched-routing-canonical (17-pick batched routing pattern; ~10x operator efficiency)
  6. NEW start-immediately-subagent-pattern-canonical (validated 3x; canonical for substantive doc-authoring subagent class)

Five codification candidates queued for next iteration close

  1. Edit-before-git-mv pitfallgit mv after Edit stages HEAD-blob; required follow-up commit (validated 2x: cli-shared + cli1)
  2. check-pii-tags-drift.sh raw URL 404s on private repos — gh api workaround canonical
  3. Worker-launch-ritual archive default to PR-route (not direct push) — classifier enforces; ritual should match
  4. Simple gh pr view poll suffices for same-iteration substrate gates — simpler than polling-loop v2 filesystem-signal pattern
  5. workers-re-anchor-origin-main-before-archive — codification candidate from Component 2 cli2 PR #218 (carry-forward)

Component 5 custodian-data scope landed (subsequent iteration's Tier 0)

PR #228 lands strategy/pwos-onboarding-component-5-custodian-data-scope-2026-05-18.md (406 lines). Quiltt PRIMARY for TradFi account aggregation (existing substrate at pw-api/src/lib/quiltt-*.ts) + Schwab direct-API SCAFFOLDING (OAuth client + smoke test; production deferred v1.5); 6 canonical actions; 8 open questions for CCO; 4-stream dispatch shape pre-staged. Authored via Agent subagent (3rd START-IMMEDIATELY validation; ~5 min wall-clock).

Operator-cost calibration

~10 operator messages for the sprint (~2 launch-paste + ~5 progress-relay + ~2 approve-routine + 1 PR-227-merge-authorization) → 12 PRs landed = ~1.2 substantive surfaces per operator-coordination touchpoint. Best velocity-per-message ratio on record. Combined day total: 32 PRs across estate for ~30 operator messages = ~1 surface per message.

Sprint retro folded here

What worked: orchestrator-direct + Agent subagent + worker-fresh-CLI hybrid at sprint scale; CCO 17-pick batched routing validated; START-IMMEDIATELY subagent pattern validated 3x; §17 SELF-FIX BOUNDARY held cleanly even on substrate-fundamental enum + constraint mismatches; 100% admin-override-aware Phase 6 flake recovery; cli1 ran 3 sequential PRs in <2 hours.

What surprised: edit-before-git-mv pitfall validated TWICE in same iteration — strong signal for next-iteration ritual codification. cli1 surfaced 2 own-task constraint mismatches + 1 latent aml-review-item.ts bug via own integration tests — pattern that integration tests catch dispatch-substrate mismatches reliably.

What's queued: Component 4 e-signature IMPLEMENTATION dispatch (Tier 0 next iteration); 5 codification candidates → worker-launch-ritual #19-#22 OR memory entries; 2 CCO TODOs (Pick 11 HITL rules + Pick 16 ADV 2A language); latent aml-review-item.ts bug fix.


2026-05-18 (Component 2 KYC sprint — 14 PRs across 4-stream parallel fanout + AML vendor canonical correction + 4 new memory anchors + Component 3 scope authored)

Sprint window ~15:35–18:43Z UTC; ~3 hours wall-clock end-to-end; 14 PRs landed across pw-shared + pw-api + pw-os-v2 + pw-portal-v2 + Component 3 risk-tolerance scope doc. Component 2 KYC verification substrate substantively complete (kyc_sessions table + Veriff + Persona shadow + Scorechain AML + advisor dashboard + client portal screens + ADRs + runbook). CCO approved all 6 Component 2 picks (PR #214; ~30min turnaround); AML vendor canonical correction (Chainalysis → Scorechain two-layer) handled mid-sprint without derailing. Component 3 risk-tolerance scope doc authored via Agent subagent with START-IMMEDIATELY mitigation (PR #223; validated the watchdog-evading pattern).

Substantive PRs by stream

PR Repo Title Stream Merged LOC
#216 pw-shared docs(component-2-kyc): ADRs + DD placeholders + KYC runbook + ADR-webhook-receiver promoted ACCEPTED cli-shared 17:06:02Z 5 new artifacts + 1 modified
#244 pw-api feat(kyc): kyc_sessions substrate + 14 canonical actions + PII_TAGS + sentinel-row reconciliation (PR-KYC-A) cli1 Task 1 17:10:31Z +1473/-1
#349 pw-os-v2 feat(kyc): advisor /clients/kyc dashboard + per-client KYC tab + bulk action BFF routes + PII_TAGS port cli2 17:18:35Z 1211 tests pass
#66 pw-portal-v2 feat(kyc): 7 portal screens + Veriff JS SDK + state-machine UI + BFF routes + PII_TAGS port cli3 17:32:20Z +1750/-2
#245 pw-api feat(kyc): Veriff client + webhook handler + Persona shadow-pilot (PR-KYC-B) cli1 Task 2 18:00:11Z +1275/-0
#220 pw-shared fix(component-2-kyc): canonicalize AML vendor — Scorechain two-layer (Chainalysis Free OFAC discontinuing) orchestrator-direct PR-fix-A 18:29:01Z +273/-287
#246 pw-api feat(kyc): AML adapter + needs_review→review_item + clients-merge DEPENDENT_TABLES fix (PR-KYC-C) cli1 Task 3 18:27:40Z +781/-0
#222 pw-shared docs(cco-approvals): AML vendor canonical clarification — Chainalysis → Scorechain orchestrator-direct PR-fix-B 18:42:58Z +44/-0
#223 pw-shared docs(component-3-risk-tolerance): scope doc DRAFT Agent subagent (validates START-IMMEDIATELY) 18:42:54Z 304 lines new

Dispatch + orchestration PRs (pw-shared)

PR #213 (4-stream dispatch pending.md) + #214 (CCO routing request — CCO approved) + #217 (cli-shared archive) + #218 (cli2 archive — incidentally squash-included PW-COMPONENT-2-KYC-CCO-APPROVAL doc) + #219 (cli3 archive) + #221 (cli1 archive — 3 tasks bundled; orchestrator re-archived after cli1's archive landed orphan on local fix/ branch).

Component 2 capabilities now live on main

  • kyc_sessions substrate at pw-api/src/db/schema/kyc-sessions.ts + migration 0049_kyc_sessions.sql + migration 0050_kyc_sessions_relax_client_id_immutability.sql (immutability relaxed to permit clients-merge UPDATE path); 17a-4 + BSA 7-year retention; BEFORE-UPDATE immutability trigger; sentinel-row reconciliation per ADR-gcs-worm R3 day-one
  • 14 canonical PW_ACTIONS at pw-api/src/lib/audit-actions.ts — 6 kyc.session.* substrate-level + 6 onboarding.kyc.* lifecycle + 2 kyc.session.retry_* sentinel pair
  • PII_TAGS.kyc_sessions canonical at pw-api; byte-equal vendored in pw-os-v2 + pw-portal-v2 per ADR-PII-tagging R3 §4 (check-pii-tags-drift.sh enforced)
  • Veriff integration: client wrapper + webhook handler (consumes Track B' webhook-receiver primitive; first production consumer; ADR promoted DRAFT → ACCEPTED) + Persona shadow-pilot logging
  • AML adapter at pw-api/src/lib/aml/component-2.ts wraps existing kyw.ts Scorechain orchestrator (Scorechain Free Sanctions API direct for OFAC + Scorechain Risk Assessment via QuickNode for KYT)
  • HALT-AND-QUEUE diagnostic closed — kyc_sessions added to clients-merge DEPENDENT_TABLES + migration 0050 relaxes immutability for merge UPDATE path
  • Advisor /clients/kyc dashboard + per-client KYC tab + 3 BFF routes (pw-os-v2)
  • 7 client portal screens + Veriff JS SDK CDN dynamic loader + 9-state client-side state machine + 3 BFF routes (pw-portal-v2)
  • ADRs landed: ADR-kyc-state-machine.md (Rev 1 → Rev 2 vendor correction) + ADR-aml-decisioning.md (Rev 1 → Rev 2 Scorechain canonical) + ADR-webhook-receiver-primitive.md (DRAFT → ACCEPTED) + ADR-gcs-worm-audit-mirror.md Rev 3 §"Applicability to future immutable tables" honored
  • Operational runbook at runbooks/kyc-verification.md (Rev 2 Scorechain canonical)
  • CCO records: PW-COMPONENT-2-KYC-CCO-REVIEW + PW-COMPONENT-2-KYC-CCO-APPROVAL (6 picks all APPROVED 2026-05-18T15:45:14Z via PR #214; AML vendor canonical clarification appended via PR #222)
  • Scorechain vendor DD placeholder (replaces deleted chainalysis-due-diligence.md; full DD next iteration)

Three pattern observations + 4 new memories codified

  1. AML vendor canonical correction handled cleanly mid-sprint. Scope doc cited Chainalysis "per memory" referring to stale state; canonical Scorechain pre-existed at docs/compliance/kyc-aml-policy.md §6 + architecture/api/scorechain.md + pw-api/src/lib/kyw.ts (Chainalysis moved off 2026-05-01 — their free Sanctions API winding down). cli1 grep against pw-api code surfaced the drift; operator + CCO confirmed canonical at 18:00Z; PR-fix-A bundled 6 doc revisions; cli1 self-corrected mid-Task-3 to wrap existing kyw.ts instead of building new Chainalysis client. Memory aml-vendor-canonical-scorechain anchors the canonical state to prevent future drift.

  2. §17 SELF-FIX BOUNDARY validated at scale. cli1 self-corrected 3+ dispatch oversights in one session without escalating: migration number drift (dispatch said 0047, cli1 used 0049); sentinel-action 3-segment regex compliance (dispatch's 4-segment → corrected to 3-segment); tenant_id + RLS added per codebase invariant; DEPENDENT_TABLES fix bundled into PR-KYC-C; mid-task Chainalysis→Scorechain pivot. Pattern: workers can self-correct dispatch-level oversights at code-author time without escalating when scope is bounded + correction is structurally honest.

  3. START-IMMEDIATELY subagent mitigation VALIDATED. Component 3 risk-tolerance scope authored via Agent subagent with START IMMEDIATELY: use TodoWrite within 30 seconds lead-in + bounded scope + reference-by-path (not in-line ADR text). Subagent streamed TodoWrite ~10s into prompt; ran ~3 min wall-clock; landed PR #223 (304 lines) clean. Companion failure (cli-shared earlier in iteration) without START-IMMEDIATELY directive watchdog-killed at 600s. Memory subagent-watchdog-prefer-fresh-cli-for-substantive-dispatch updated with validation.

4 new memories codified this iteration:

  • aml-vendor-canonical-scorechain (canonical state anchor; Chainalysis moved off 2026-05-01; Scorechain two-layer canonical)
  • workers-re-anchor-origin-main-before-archive (codification candidate for worker-launch-ritual #19; cli2 PR #218 squash-include precedent)
  • subagent-watchdog-prefer-fresh-cli-for-substantive-dispatch (subagent fit pattern + START-IMMEDIATELY mitigation)
  • orchestrator-pr-merge-standing-authorization (operator durable instruction: orchestrator merges PRs when CI/CD green + tests pass)

Five-stream + orchestrator-direct coordination held cleanly

  • §15 multi-stream parallel fanout at 4-stream worker scale + orchestrator-direct + Agent subagent; cross-stream polling gate (cli2 + cli3 polled for cli1 PR-KYC-A merge); both unblocked within 7-min polling budget; Agent subagent for Component 3 scope ran in background while orchestrator authored iteration-close hygiene + PR-fix-A
  • §16 per-worker worktree held cleanly across all 4 worker streams + the Agent subagent's isolation=worktree (auto-managed by Agent tool)
  • §17 SELF-FIX BOUNDARY held + validated at scale (pattern observation #2)
  • §20 fetch-before-grep applied: every cross-repo claim verified via git fetch origin
  • Ritual #9 classifier denial held: cli2 hit git pull origin main classifier denial → cli2 surfaced + self-resolved after operator authorization; orchestrator hit classifier denial on cco-approvals/ PR merge (PR #222) → held + operator granted standing authorization for future routine merges; orchestrator hit classifier denial on parallel-Bash with PR merge → cancelled the bundled subagent spawn + re-spawned subagent solo
  • Ritual #18 AskUserQuestion delivered ~22 picks total across the iteration at ~30s decision latency each (~10-12 min total pick latency vs 3h wall-clock)

Component 3 risk-tolerance scope authored (next-iteration Tier 0 unblock)

PR #223 lands strategy/pwos-onboarding-component-3-risk-tolerance-scope-2026-05-18.md (304 lines) — in-house FRTS-13 + PW overlay v1 questionnaire with Riskalyze deferred to v2; 1-100 composite score → conservative/moderate/aggressive bucket; risk_tolerance_sessions state machine mirroring kyc_sessions; sentinel-row reconciliation day-one per ADR-gcs-worm R3; 6 canonical actions; 3-year re-assessment cadence baseline; HITL Tier 2 on outlier responses; 6-year retention per Rule 204-2(a)(8); 4-stream dispatch shape pre-staged; 8 open questions queued for CCO. Authored via Agent subagent in ~3 minutes.

Operator-cost calibration

~25-30 operator messages for the sprint → 14 PRs landed = ~2 substantive surfaces per operator-coordination touchpoint. Excellent tier per substrate-patterns rubric Category 8. New sprint-velocity record on the 4-stream-parallel pattern + orchestrator-direct + Agent subagent hybrid.

Deferred follow-up + carry-forward

  • 5 integration tests deferred (cli1 PR-KYC-C) — expected-verified + needs-review + AML-hit-escalation + Persona-divergence + Veriff-down failover paths; require real staging Veriff sandbox + ephemeral PG
  • 9 + 8 CCO open questions in ADRs — 9 from Component 2 (risk-score threshold; PEP escalation order; Scorechain plan tier trigger; ongoing-monitoring cadence; state-specific obligations; manual review SLA; Persona day-30/90 review; v2 on-chain proof-of-ownership; multi-jurisdiction KYC) + 8 from Component 3 (re-assessment cadence; HITL threshold; question-bank source; score-to-bucket thresholds; advisor override authority; reg citation verification; ADV 2A Item 8 integration; cross-bucket override gate). Bundle with next CCO touchpoint.
  • clients-merge.spec.ts:368 flake observed twice (PR-KYC-B + PR-KYC-C); rerun-retry resolved; tracked separately
  • Component 3 IMPLEMENTATION dispatch — pre-staged at next-iteration-briefing.md; Tier 0 next iteration

Sprint retro folded here

What worked: multi-stream parallel fanout at 4 streams + orchestrator-direct + Agent subagent hybrid; CCO turnaround in <10 min via canonical REVIEW/APPROVAL doc-PR pattern; AML vendor canonical correction handled mid-sprint without derailing; START-IMMEDIATELY subagent mitigation validated for substantive doc authoring; §17 SELF-FIX BOUNDARY held cleanly under scale.

What surprised: scope doc's "per memory" Chainalysis citation propagated through briefing → CCO review → ADRs before cli1's grep caught it; memory anchors added to prevent future propagation. Subagent watchdog killed substantive dispatch (cli-shared) without START-IMMEDIATELY; with START-IMMEDIATELY, same scope class (Component 3) ran clean. Pattern is task-specific not class-specific.

What's queued: Component 3 risk-tolerance IMPLEMENTATION dispatch (Tier 0 next iteration); Component 4 e-signature scope authoring; Reg S-P PDFs awaiting CCO DocuSign (June 3 deadline carry-forward).


2026-05-18 (compliance hardening sprint — 6-PR audit-finding closure across 4 worker streams + §12.3 sentinel-row codification + AskUserQuestion canonical Phase 1.5 delivery)

Sprint window 13:15–14:30Z UTC; ~130 min wall-clock end-to-end; 6 substantive PRs + 8 dispatch infrastructure PRs landed across pw-shared + pw-api + pw-os-v2 + pw-portal-v2. Closes audit-2026-05-17.md (PR #202) HIGH findings T1.1 + T1.2 + T1.3 + T1.4 + T1.5 + 2 MEDIUMs + 2 unwired-guard gaps. Hardened compliance baseline ready for Component 2 KYC dispatch on next iteration.

Substantive PRs by stream

PR Repo Title Stream Merged LOC
#204 pw-shared feat(governance): canonical PII_TAGS drift script + ADR-PII-tagging §4 + ADR-gcs-worm Revision 2 + rule-17a-4 evidence-trail update cli-shared (PR-E) 13:15:41Z +266/-1
#240 pw-api feat(pii-tags): Turnkey + onchain client-financial coverage (audit T1.3) cli1 Task 1 (PR-A1) 13:35:55Z (admin-override)
#347 pw-os-v2 feat(pii+observability): port pw-api PII_TAGS canonical + middleware honest-relabel (audit T1.2 + T2) cli2 (PR-C) 13:47:41Z +941/-193
#63 pw-portal-v2 feat(pii+observability): port + relabel + requestIdMiddleware (audit T1.2 + T2 + T1.4) cli3 (PR-D) 13:51:49Z +1370/-177
#242 pw-api feat(audit): canonical-action guard + actor_email required + missing PW_ACTIONS (audit T1.5 + MEDIUMs) cli1 Task 2 (PR-A2) 14:04:07Z +194/-21
#243 pw-api feat(audit): WORM mirror broad audit_log coverage (audit T1.1) — §12.3 sentinel-row pivot cli1 Task 3 (PR-B) 14:28:36Z +722/-20

Dispatch infrastructure PRs (pw-shared)

PR #203 (4 pending.md dispatch inbox) + #205 (cli-shared archive) + #206 (cli1 failed-ci archive — predecessor) + #207 (cli1 continuation pending.md) + #208 (cli2 archive) + #209 (cli3 archive) + #211 (cli1 continuation archive).

Audit findings closed

  • T1.1 GCS WORM mirror extended to broad audit_log coverage — writeAuditMirror({eventType: 'audit_log', ...}) invoked from inside writeAuditLog() post-INSERT; every audit_log row mirrors to gs://pwllc-audit-archive regardless of caller; recursion guard prevents sentinel-row infinite loops; reconciliation cron daily 02:00 UTC; fail-soft posture preserves audit_log INSERT semantics on transient GCS failure
  • T1.2 PII_TAGS canonical cross-repo alignment — pw-os-v2 + pw-portal-v2 ported byte-aligned to pw-api canonical (post-PR-A1); local-fixture sync test retired in favor of shared/scripts/check-pii-tags-drift.sh canonical + vendored-copy SHA-256 self-canary in each BFF CI
  • T1.3 Turnkey + onchain PII_TAGS coverage — client_turnkey_bindings + client_turnkey_recovery_events + 11 onchain client-financial tables added
  • T1.4 Request-id propagation — requestIdMiddleware added to pw-portal-v2; x-request-id forwarded by pwApiPost + lookupPortalByEmail + chat-tools.callPwApi; portal-originated audit_log rows no longer have request_id = null
  • T1.5 Actor-attestation completeness — actor_email required across /v1/internal/* mutation routes; isCanonicalAction() helper invoked at top of writeAuditLog() (THROW dev/test; WARN-loud prod); 9 inline-action strings replaced with PW_ACTIONS constants (5 audit-cited + 2 governance-additional + 2 strict-3-segment renames); parseAction + verb-regex aligned (3-segment canonical)
  • T2 unwired pii-prompt-construction middleware — both BFFs found ZERO row-shaped SDK call sites needing wiring; correctly relabeled file headers to honest-report substrate-awaiting-consumer state rather than wiring fictional consumers (audit's framing assumed code-wiring was the fix; truth was header-correction was the structurally honest fix)
  • MEDIUMsgovernance.action.taken + governance.vote.cast inline → PW_ACTIONS constants; actor_email Zod-optional inconsistency resolved

Three pattern observations worth codifying

  1. §12.3 good-stop fired live on cli1 PR-B. Phase 1 baseline surfaced audit_log is DB-immutable (BEFORE UPDATE trigger raises) — a constraint the dispatch didn't pre-stage in its Phase 1.5 picks. cli1 correctly escalated via AskUserQuestion; operator picked Approach C (insert-time-only + sentinel rows). Pattern: when Phase 1 baseline surfaces a premise-invalidating constraint mid-Phase, escalate to Phase 1.5 STOP even if dispatch didn't pre-stage that exact pick. Memory candidate: phase-1-5-stop-retroactive-escalation.

  2. Sentinel-row pattern is canonical reconciliation shape for WORM/immutable tables. Reconciliation MUST NOT UPDATE failed rows on tables with BEFORE-UPDATE-triggers (audit_log, review_actions, ai_runs, future kyc_verifications, future signed_document_archive). Instead, INSERT a NEW row (sentinel) referencing the failed row's ID + retry attempt + outcome. Preserves immutability invariant + still enables defense-in-depth coverage of mirror-write failures. Documented at ADR-gcs-worm-audit-mirror.md Revision 3. Memory candidate: sentinel-row-immutable-reconciliation-pattern.

  3. AskUserQuestion is the canonical Phase 1.5 STOP delivery mechanism. Workers used AskUserQuestion across 5 hardening-sprint Phase 1.5 stops (cli-shared 5 picks + cli2 4 picks + cli3 3 picks + cli1 Task 2 3 picks + cli1 Task 3 4 picks). ~30s decision latency per pick; ~20 pick resolutions for 6 PRs at ~10 min total pick latency vs ~130 min sprint wall-clock. Codified as worker-launch-ritual item #18. Memory candidate: askuserquestion-phase-1-5-canonical-delivery.

Five-stream coordination via §15-§21 ritual

  • §15 multi-stream parallel fanout validated at 4-stream scale (cli1 + cli2 + cli3 + cli-shared) with cross-stream polling-loop gate (cli2 + cli3 polled for cli1 PR-A1 merge via gh pr list --search pattern match; both unblocked at 13:36Z within polling-budget)
  • §16 per-worker worktree held cleanly across all 4 streams; 4 worktrees created + cleaned up post-Phase-7
  • §17 SELF-FIX BOUNDARY held: cli2 + cli3 correctly relabeled file headers rather than wiring fictional consumers (honest-report acceptance); cli1 surfaced architectural constraint via §12.3 rather than silently absorbing
  • §20 fetch-before-grep applied: every cross-repo claim (pw-api canonical reads from pw-os-v2 + pw-portal-v2 ports) refreshed via git fetch origin
  • §21 case-naming awareness applied: cli2 + cli3 surveyed jq output structure before scripting filters

Two pre-existing main CI failures handled

  • pw-api Migration dry-run RED on clients-merge.spec.ts:368 (pre-existing from PR #239); blocked cli1's PR-A1 auto-merge; operator force-merged via admin-override at 13:35:55Z. cli1 continuation pending.md added admin-override-aware Phase 6 protocol; PR-A2 + PR-B subsequently auto-merged cleanly (failure was specific to PR #239 content, not Tasks 2+3 paths). Tracked separately for follow-up resolution.
  • pw-portal-v2 self-canary trailing-newline bug introduced by PR-D #63 itself (cli3 surfaced + correctly left out-of-scope); filed as shared/#210; ~10 LOC mechanical fix tracked as compliance-hardening-tier-1-2-2026-05-18-followup fast-follow (pw-os-v2 + pw-portal-v2 CI workflow edits in flight via orchestrator-internal Task subagents this iteration).

Operator-cost calibration

~30 operator messages for the sprint (~5 launch-paste + ~20 pick-resolution + ~5 admin-override + miscellaneous) → ~5 substantive outcomes per operator message. "Excellent <15-message-block" tier per substrate-patterns rubric Category 8.

Sprint retro folded here (no separate doc per operator directive)

What worked: multi-stream parallel fanout at 4 streams; AskUserQuestion tightened pick-resolution latency vs chat-narration; ritual #15 PR-route fallback fired correctly twice (cli-shared + cli3 archives); §17 honest-report acceptance held cleanly across all worker dispatches.

What surprised: audit drift estimates were conservative (cli2 + cli3 baseline diffs both exceeded ±5 gate); the T2 "wire middleware" finding turned out to be a header-correction not code-wiring — workers discovered via Phase 1 enumeration; Component 2 KYC compliance baseline now substantially stronger than the dispatch authored against.

What's queued: fast-follow #210 self-canary fix (in flight); Component 2 KYC dispatch (gated on operator return + kyc_sessions-vs-kyc_verifications reconciliation decision).


2026-05-17 (post-dogfood vendor-DD update — Anthropic ZDR verified evidence + Trust Portal references + CCO review_item bootstrap queued)

First substrate dogfood loop closed 2026-05-17 ~18:37Z with ai_run 3bfd0f73-4297-4dc6-9789-60d6a4ea6c69 (upstream msg_0167ULtBanVNFiDiNqz6GpDZ) — fail-closed ZDR validation at pw-api/src/governance/claude-client.ts:108 exercised end-to-end; ANTHROPIC_ZDR_CONFIRMED=true bound on pw-api Cloud Run revision pw-api-00233-lwc (us-central1); ai_runs.zdr_confirmed CHECK constraint held; cost 0.2591 USD; 1321 in / 3190 out tokens; 44.169s elapsed; 11 flagged_issues at confidence 55. Vendor DD doc moved from DRAFT (placeholder-pending) to ACTIVE v1.1 reflecting verified operational evidence rather than aspirational language.

  • pw-shared (THIS PR) — vendor-DD verified ZDR update. compliance/vendor-correspondence/anthropic-due-diligence.md v1.1 ACTIVE: replaced all _pending CCO confirmation_ placeholders with verified evidence — GTM contact Pascale Richards [email protected]; notice email 2026-04-21 12:04 AM ("Protocol Wealth LLC ZDR" — verbatim quote retained); effective date 2026-04-23 (1-2 business days post-notice); console verification at https://platform.claude.com/settings/privacy on ~2026-04-23 + re-verification 2026-05-17 ~18:30Z confirming 0-day retention + US Inference Geo + US Workspace Geo (immutable) + user feedback DISABLED + Claude Code metrics logging DISABLED. New sections: "ZDR enrollment — verified evidence" + "Operational substrate-side attestation" (Cloud Run env binding + fail-closed validation + DB CHECK constraint defense-in-depth + first ai_run identifiers) + "External compliance artifacts (Anthropic Trust Portal)" (SOC 2 / ISO 27001 / ISO 42001 / GDPR DPA / Subprocessor list / pen-test refs; retention plan at gs://pwllc-audit-archive/vendor-dd/anthropic/ reserved; review cadence). Approval table: CTO/CISO signed 2026-05-17 (operator authority); CCO pending via substrate-mediated compliance_doc review_item (companion pw-api migration). Annual review cadence anchored to ZDR effective date (next 2027-04-23).

  • compliance/vendor-correspondence/evidence/ directory scaffolded (.gitkeep + README.md covering naming convention + retention posture + PII discipline). Operator commits actual screenshots (anthropic-zdr-notice-pascale-richards-2026-04-21.png + anthropic-console-privacy-2026-05-17.png + anthropic-console-security-geo-2026-05-17.png) separately.

  • runbooks/governance-review-primitive.md updated — Manual Verification Path Step 2 now cross-references vendor-DD verified evidence + canonical first ai_run identifier; ZDR Contract Failure recovery path names console verification URL + the canonical vendor-DD doc.

  • Companion pw-api migration queued (PR 2). Bootstrap compliance_doc review_item in review_items table — surfaces CCO acknowledgment via the substrate-mediated governance flow rather than out-of-band email. Migration depends on this PR's merged content for SHA-256 content_hash computation; opens after this PR lands.


2026-05-16 (evening-block FINAL-ANCHOR close — full evening arc: PR-Z4-portal + lifecycle: v2 flip + Wealthbox inbound sync + Gate 2 + Gate 3 + design doc + design doc revisions; substrate essentially complete; bridge-work design landed; STAND DOWN to tomorrow morning's Track 0 partner-prep)

Block 3 of 2026-05-16 same-day continuation iteration. ~2.5-hour wall-clock window (21:45–24:00Z UTC). ~12 PRs across the evening block (pw-shared #178/#179/#180/#181/#182/#183/#184 + pw-api #222 + pw-portal-v2 #60 + Gates 2+3 + design doc revisions). Substrate essentially complete after Wealthbox inbound sync (PR #222) lands; bridge-to-product mode transitioned at block close; design doc + 6 revisions covering 8 MVP components landed. Tomorrow morning's iteration opens with Track 0 partner-prep deliverables (1-page visual summary + CCO questionnaire + CIO questionnaire) ahead of Phase 1 engineering dispatches.

  • pw-portal-v2 #60 MERGED 2026-05-16T21:57:18Z — PR-Z4-portal middleware port (subagent dispatch; mirror of pw-os-v2 #336 actual landed scope). Subagent's Phase 1 diagnostic surfaced briefing-overstated-PR-scope finding; codified as new memory briefing-summaries-may-overstate-pr-scope.md (Gate 3 mutation).

  • pw-shared #178 MERGED 21:55:34Z — bundled dispatch+lifecycle PR (orchestrator-direct). Four surfaces: cli2 Wealthbox inbound sync pending.md + lifecycle.md §9 Q1+Q6 settlement (per-worker git-worktree + awaiting: operator-decision canonical) + worker-launch-ritual.md #7 cross-reference + orphan-branch safety doc.

  • pw-shared #179 MERGED 22:01:02Z — substrate-patterns 8-category rubric (strategy/substrate-patterns-2026-05-16.md; 171 lines).

  • pw-shared #180 MERGED 22:17:22Z — cli2 wealthbox-inbound-sync claim + Phase 1.5 SURFACE archive. Operator picked 1d/2a/3b/4a (manual-only / embedded API path / dual-write / malformed-only audit emit); all 4 matched orchestrator pre-stage recommendations.

  • pw-api #222 MERGED 22:55:43Z — Wealthbox inbound NPI sync producer-side path (cli2 dispatch). 372 LOC src/lib/wealthbox-npi-sync.ts + 82 LOC route handler + 397 LOC tests. Consumes PR #221 skeleton end-to-end: discrete columns + canonical PII_TAGS extension + audit-action constant WEALTHBOX_NPI_BACKFILL_MALFORMED all exercised by producer-side path. Dual-writes to discrete columns + clients.metadata keys during migration window.

  • pw-shared #181 MERGED 22:58:20Z — cli2 wealthbox-inbound-sync completed archive.

  • Gate 2 — 23 shared/ remote orphan branch batch cleanup EXECUTED 22:02Z (operator-authorized binary go). 21 explicit deletes + 2 already-auto-deleted between safety-list-capture and execution (PR #176/#177 source branches; --delete-branch lineage). 23/23 target branches absent; remote pw-shared has exactly 1 branch (main).

  • Gate 3 — 2 memory mutations applied 22:05Z. (1) NEW memory briefing-summaries-may-overstate-pr-scope.md (gh pr view --json files verification discipline). (2) UPDATE memory lifecycle-v2-rollout-3-of-3-pattern.md with 6-consecutive-clean-dispatches-post-flip evidence (3 opt-in + 3 post-flip). MEMORY.md index entry added. /tmp/dream-pass-output-2026-05-16.md archived post-application.

  • pw-shared #182 MERGED 23:11:02Z — evening-block CHANGELOG entry (this entry's predecessor; intermediate-state capture).

  • pw-shared #183 MERGED 23:19:35Z — PWOS onboarding scope design doc (strategy/pwos-onboarding-scope-2026-05-16.md; 483 lines; 7 sections covering substrate-to-onboarding mapping + 7 MVP components + Anvil sub-tasks + sequencing + ASAN frame + CCO gates + timeline). Substrate inventory subagent (background; ~5 min cycle) provided capability inventory; orchestrator-direct synthesis layered worker-cycle estimates + CCO gate batching guidance.

  • pw-shared #184 (THIS PR) — design doc revisions + final-anchor CHANGELOG + CURRENT-STATE refresh + briefing rewrite. 6 design doc revisions per operator directive: (1) Sub-task C7 webhook-receiver primitive ADR pointer (Phase 1 Track B' first deliverable; status DRAFT pending CTO/CISO review tomorrow); (2) Component 4 e-delivery consent acceptance criterion + compliance.disclosure.e_delivery_consented canonical action; (3) Component 5 custodian architecture inversion (v1 manual handoff at Schwab/Altruist/IBKR's own flows + custodian-account-number capture + Quiltt sync activation; Altruist/IBKR API account-opening deferred to v2; ~3-4 cycles → ~1-2 cycles); (4) Component 8 IPS generation added (CFA-Institute-style sections + CIO methodology gate + 4 new canonical actions; total registration up to ~33 actions); (5) PW-direct ACH alternative deferred to v2 backlog (one-sentence Component 6 note); (6) Section (a) substrate map updated to surface Component 8 + Wealthbox NPI inbound sync use cases. Timeline tightened 4-8 → 4-7 weeks per custodian inversion.

  • Substrate validation through evening block. 6 consecutive clean lifecycle: v2 dispatches now stretches to 7 (PR-Z2 + PR-Z3 + PR-D1 opt-in + PR-Z4 + Wealthbox NPI #221 + PR-Z4-portal pw-portal-v2 #60 + Wealthbox inbound sync pw-api #222 post-flip). Worker-extends-pre-stage pattern held across all 3 post-flip dispatches (Pick 7(b) canary-revert; Pick 0 architectural-premise reframe; PR-Z4-portal scope-correction). Per-dispatch operator-touchpoint cost held at 1-2 messages. Operator-cost calibration this evening block: ~12 operator messages for ~12 PRs + 2 gate-ops + 2 memory mutations + design doc + 6 revisions = ~1.3 substantive outcomes per operator message; firmly within "Excellent <15-message-block" tier per substrate-patterns rubric Category 8.

  • Transition to bridge-to-product mode complete at block close. Substrate essentially complete after Wealthbox inbound sync (PR #222) lands. Bridge-work design landed (PR #183 + revisions in #184). Tomorrow morning's iteration opens with Track 0 partner-prep deliverables ahead of Phase 1 implementation dispatches: 1-page visual summary of the 8-component onboarding flow + CCO questionnaire (gate sequencing prefs + copy review prefs + compliance posture confirmation) + CIO questionnaire (IPS methodology prefs + EMF integration framing + capital-structure handoff prefs). Output paths: shared/strategy/pwos-onboarding-partner-prep-2026-05-17.md + shared/strategy/partner-questionnaire-adam-2026-05-17.md + shared/strategy/partner-questionnaire-jason-2026-05-17.md. Subsequent Phase 1 Track B' (webhook-receiver primitive ADR; CTO/CISO review gate) + Track A (Component 1 portal auth) + Track C (~33 canonical-action registration) fire after Track 0 lands.

  • Carry-forward to tomorrow's iteration: Track 0 partner-prep deliverables (orchestrator-direct + subagent); Phase 1 Track A/B'/C/D/D' engineering + compliance/methodology gate sequencing; 22 shared/ remote orphan branches → 0 (cleanup complete; no carry-forward); 2 memory mutations applied; lifecycle: v2 substrate at 7 consecutive clean dispatches.


2026-05-16 (evening-block completion — Wealthbox inbound sync producer-side path lands; substrate essentially complete; transition to bridge-to-product mode)

Same-day continuation third block (~2-hour window 21:45-23:00Z, ~1h after the iteration-close anchor PR #177). 7 PRs landed across pw-shared + pw-api + pw-portal-v2 + 2 orphan-cleanup gate operations + 2 memory mutations applied. Substrate essentially complete after PR #222 landing — Wealthbox inbound NPI sync makes the PR #221 skeleton useful; producer-side path enables the natural follow-on that previously only existed as scoping doc. Operator transitioned to bridge-to-product mode at block close.

  • pw-portal-v2 #60 MERGED 2026-05-16T21:57:18Z — PR-Z4-portal middleware port. Subagent dispatch (worktree-isolation; feat/stream-z-pr-z4-portal-middleware). Ports pw-os-v2 PR-Z4 PII-tagging middleware to pw-portal-v2: apps/api/src/lib/pii-tags.ts (174 LOC canonical map mirror) + pii-prompt-construction.ts (422 LOC excludeHighPii middleware) + pii-tags-sync.test.ts (162 LOC drift detector) + audit-actions.ts (42 LOC minimal mirror). Anthropic egress in anthropic-client.ts (raw fetch, not SDK) deliberately untouched per canary-vs-classifier-route-conflict memory; middleware lands as structural guard ready for future per-row callers (portfolio summarization, account narration). Subagent's Phase 1 diagnostic surfaced briefing-overstated-PR-scope finding: the iteration-close briefing described pw-os-v2 #336 as wrapping "5 SDK call sites in claude-client.ts," but gh pr view 336 --json files showed the actual landed scope was middleware infrastructure + broadcasts.ts wrap + inbound-ai-classifier.ts wrap (later reverted). Subagent correctly mirrored landed-scope not briefed-scope. New memory artifact briefing-summaries-may-overstate-pr-scope.md codifies the gh pr view --json files verification discipline for any port/mirror dispatch.

  • pw-shared #178 MERGED 2026-05-16T21:55:34Z — bundled dispatch+lifecycle PR (orchestrator-direct). Four surfaces in one PR per CLAUDE.md §1.4 routine-doc-maintenance policy: (1) dispatch/cli2/pending.md Wealthbox inbound sync producer-side path dispatch with 4 Phase 1.5 picks pre-staged; (2) dispatch/protocol/lifecycle.md Phase 1 ADOPTION sub-items (b)+(c) settled — §9 Q1 codifies per-worker git worktree as canonical structural fix retiring ritual #7 in Phase 2; §3.2+§9 Q6 codify awaiting: operator-decision as canonical Phase 1.5 STOP enum value superseding the transitional operator-pick term; (3) dispatch/shared/worker-launch-ritual.md item #7 cross-reference appended naming the canonical per-worker-worktree pattern; (4) strategy/orphan-branch-cleanup-batch-2026-05-16.md safety capture doc for the 23 shared/ remote orphan branches (all verified MERGED with --delete-branch lineage).

  • pw-shared #179 MERGED 2026-05-16T22:01:02Z — substrate-patterns 8-category rubric durable artifact. strategy/substrate-patterns-2026-05-16.md (171 lines). Persists the 8-category cross-iteration substrate-patterns rubric distilled from 2026-05-16 same-day continuation iteration close. Functions as canonical decision-making framework for orchestrator routing + dream-pass memory analysis. Categories cover multi-stream parallelization / worker-extends-pre-stage / ritual hardening compounding / lifecycle: v2 rollout pattern / CCO-CTO gate framing / substrate-questions-resolve-via-investigation / architecturally-exempt-needs-memory-artifacts / operator-cost calibration. Companion application output at /tmp/dream-pass-output-2026-05-16.md (per-iteration application; deleted post-Gate-3 mutations).

  • pw-shared #180 MERGED 2026-05-16T22:17:22Z — cli2 wealthbox-inbound-sync claim + Phase 1.5 SURFACE archive. cli2 worker's interim archive of Phase 1.5 SURFACE state. cli2 surfaced 4 picks per dispatch + the operator picked 1d / 2a / 3b / 4a (manual-only / embedded API path / dual-write semantics / malformed-only audit emit). All 4 matched orchestrator pre-stage recommendations.

  • pw-api #222 MERGED 2026-05-16T22:55:43Z — Wealthbox inbound NPI sync producer-side path. cli2 dispatch (post-flip lifecycle: v2 default-on). 372 LOC new module src/lib/wealthbox-npi-sync.ts exporting syncContactNpiFromWealthbox(contactId, deps) with Zod-validated cast-with-fallback per column type; 82 LOC new route handler at POST /v1/internal/wealthbox/contacts/:id/sync-npi (system-tenant auth); 397 LOC tests covering happy-path + malformed-cast per column type + idempotency + Wealthbox response shape drift. Emits WEALTHBOX_NPI_BACKFILL_MALFORMED audit-action on cast failure (constant already registered in PR #221 skeleton). Dual-writes to discrete columns AND clients.metadata keys during migration window per design spec §3 step 2. PR #221 skeleton now consumed end-to-end: discrete columns + canonical PII_TAGS map extension + audit-action constant all exercised by an actual producer-side path. Future operator-direct PR removes metadata writes after ~1 stable iteration confirms the discrete-column path.

  • pw-shared #181 MERGED 2026-05-16T22:58:20Z — cli2 wealthbox-inbound-sync completed; archived. Terminal completion archive at dispatch/cli2/done/2026-05-16-225727-completed.md.

  • Gate 2 — 23 shared/ remote orphan branch batch cleanup EXECUTED 2026-05-16T22:02Z. Operator-authorized binary go (per memory classifier-denial-on-bulk-destructive per-action authorization). Loop executed: 21 explicit deletes succeeded; 2 reported Reference does not exist (PR #176/#177 source branches; already auto-deleted between safety-list capture and execution via --delete-branch lineage). 23/23 target branches absent. Post-execution git ls-remote origin 'refs/heads/*' minus main returns empty — remote pw-shared has exactly 1 branch (main). Memory orphan-cleanup-must-exclude-open-pr-branches safety check held cleanly (pre-execution gh pr list --state open returned []).

  • Gate 3 — 2 memory mutations applied 2026-05-16T22:05Z. (1) NEW memory artifact ~/.claude/projects/-home-nick-projects-pw-shared/memory/briefing-summaries-may-overstate-pr-scope.md (55 lines) codifies the gh pr view <N> --json files,additions,deletions verification pattern for any port/mirror/cross-repo-expansion dispatch — surfaced by PR-Z4-portal subagent's diagnostic correction of briefing's PR-Z4 framing. (2) UPDATE existing memory lifecycle-v2-rollout-3-of-3-pattern.md extends with 6-consecutive-clean-dispatches-post-flip evidence section (PR-Z2 + PR-Z3 + PR-D1 opt-in + PR-Z4 + Wealthbox NPI #221 + PR-Z4-portal pw-portal-v2 #60 post-flip). Original 3-of-3 framing preserved as historical anchor; updated description names 3-opt-in-plus-3-post-flip substrate-scaling validation. MEMORY.md index entry added for the new artifact. /tmp/dream-pass-output-2026-05-16.md archived (deleted post-application).

  • Lifecycle: v2 substrate scaling validated at 6 consecutive clean dispatches. PR-Z2 + PR-Z3 + PR-D1 + PR-Z4 + Wealthbox NPI #221 + PR-Z4-portal pw-portal-v2 #60. The post-flip 3 dispatches confirm the substrate holds beyond the rollout window. Worker-extends-pre-stage pattern held cleanly across all 3 post-flip dispatches (Pick 7(b) canary-revert; Pick 0 architectural-premise reframe; PR-Z4-portal scope-correction). Per-dispatch operator-touchpoint cost held at 1-2 messages. Same-day continuation pattern viable — 3 of the 6 dispatches landed within a single 2-hour same-day continuation block; substrate handles rapid throughput without quality degradation.

  • Transition to bridge-to-product mode at block close. Operator-directive: substrate essentially complete after Wealthbox inbound sync (PR #222) lands; natural next work is the bridge from substrate to client-facing utility. Formal iteration close (CURRENT-STATE refresh + briefing rewrite) deferred to end of bridge-work block. Next active work: PWOS onboarding scope design doc at shared/strategy/pwos-onboarding-scope-2026-05-16.md — substrate-to-onboarding mapping + 7 MVP onboarding components + Anvil integration sub-tasks + sequencing recommendation + ASAN evaluation + CCO compliance gates + timeline. Tomorrow's iteration opens with FIRST implementation work that produces end-product utility — CTO/CISO + CCO + CIO use PWOS for actual client work within ~4-8 weeks of focused engineering.

  • Operator-cost calibration. ~7 operator messages this block ([launch + Phase 1.5 picks operator response + Gates 2+3 authorization + PR #222 confirmation + transition directive]; within "Excellent <8" tier per substrate-patterns rubric Category 8). 7 PRs + 2 gate-ops + 2 memory mutations = 11 substantive outcomes per ~7 operator messages = ~1.6 outcomes per operator message. Calibrated pre-staging + worker-extends-pre-stage held the cost down through the block.


2026-05-16 (same-day continuation close — Stream Z FULLY CLOSED via PR-Z4 + Wealthbox NPI skeleton + lifecycle: v2 codification + Dependabot grooming + worktree hygiene)

Same-day continuation iteration opened ~1h after the prior iteration's 19:11Z stand-down. 7 PRs landed across pw-shared / pw-os-v2 / pw-api / pw-website + 1 PR closed + worktree+branch hygiene sweep + capacity-permitting Dependabot grooming. Stream Z FULLY CLOSED in production: PR-Z4 (pw-os-v2 middleware port) closes the structural-guard gap on pw-os-v2's claude-client.ts — the actual production Anthropic SDK call surface (5 egress sites). pw-os-v2 now has all 4 defense layers in production.

  • pw-os-v2 #336 MERGED 2026-05-16T20:14:26Z — PR-Z4 port prompt-construction exclusion middleware. cli1 dispatch (first post-flip lifecycle: v2 default-on dispatch). Ports pw-api PR-Z3 reference implementation to pw-os-v2's apps/api/src/lib/claude-client.ts. Operator picks at Phase 1.5: (1b) port canonical PII_TAGS typed map + sync test; (2b) module-level wrap mirroring PR-Z3 structural shape; (3) ESLint custom rule extending pw-os-v2's existing config with no-restricted-syntax selectors; (4c) defer PII_WAIVER_SECRET provisioning to separate operator-direct terraform PR; (5) accept canonical audit-event names exactly. cli1 surfaced an additional Pick 7(b) at Phase 1.5: existing PR-Z3 middleware wrap of inbound-ai-classifier.ts would trigger the egress canary on test fixtures with identifier-bearing sender emails (e.g., [email protected]). Operator picked Pick 7(b) reverted-wrap pattern: keep broadcasts wrap, revert classifier wrap. Memory artifact canary-vs-classifier-route-conflict.md captures the conflict class for future PR-Z4-portal + similar wraps.

  • pw-api #221 MERGED 2026-05-16T20:04:32Z — Wealthbox NPI promotion to discrete columns (skeleton). cli2 dispatch. Critical Phase 1 finding: design spec at strategy/wealthbox-npi-fields-promotion-plan.md (PR #163) assumed inbound Wealthbox custom_fields → clients.metadata sync that DOES NOT EXIST in pw-api today — Wealthbox integration is OUTBOUND-only (src/lib/wealthbox.ts + src/lib/dispatcher.ts push to Wealthbox; contactResponseSchema.passthrough() doesn't whitelist custom_fields; no code path reads custom_fields from GET responses or writes to clients.metadata). cli2 surfaced a NEW Pick 0 (architectural premise) before the 5 design-spec picks; operator picked Pick 0(a) ship structural skeleton only — 5 new typed columns + Drizzle mirror + canonical PII_TAGS map extension + audit-action constant + Zod schema-drift guards; NO backfill UPDATE (no source data) + NO ingest dual-write (no inbound ingest path). Documented deferred dual-write in PR body as follow-up gated on the future inbound sync PR. The skeleton has standalone value (downstream PRs write directly to discrete columns instead of metadata-blob) and avoids speculative implementation. Picks 1, 2, 5 stood as recommended; Picks 3 + 4 N/A under Pick 0(a).

  • pw-shared #172 MERGED 2026-05-16T19:42:21Z — dispatch activations (cli1 PR-Z4 + cli2 Wealthbox NPI). Single PR carrying both worker pending.md files. PR-Z4: git mv from dispatch/shared/pr-z4-staged-dispatch.md to dispatch/cli1/pending.md + preamble strip + dispatched_at: 2026-05-16. Wealthbox NPI: drafted cli2 pending.md from 197-line scoping plan (PR #163) using canonical Phase 0-6 dispatch shape; 5 Phase 1.5 picks pre-staged with recommendations.

  • pw-shared #173 MERGED 2026-05-16T19:52:38Z — lifecycle: v2 default-on flip codification. Decision 1 fired post-PR-D1 merge; 3 substrate docs updated: dispatch/protocol/lifecycle.md (§1 status flip "Phase 1 design — opt-in" → "Phase 1 default-on; opt-in window CLOSED 2026-05-16; lifecycle: v2 canonical on all worker dispatches" + §8 migration path split: §8.1 historical opt-in reference + §8.2 default-on CURRENT with substrate validation evidence: state machine integrity / Phase 1.5 STOP discipline / frontmatter atomicity / operator-touchpoint cost + §9 open questions Q6/Q7/Q8 updated with observations from the 3-dispatch rollout window + footer restates substrate validation); strategy/dispatch-next-iteration-queue.md (Queue A row splits Phase 1 ADOPTION work into completed sub-item (a) + carry-forward sub-items (b)+(c); Phase 2 unblocked; Iteration handoff reframes opt-in narrative; Decisions 1/2/3/4 marked FIRED/SHIPPED/HOLD LIFTED/CLEARED); strategy/orchestrator-daily-start-prompt.md (foundational tier + time vocabulary + cold-start file list + worker dispatch step 1 frontmatter shape + polling-loop v2 footer).

  • pw-shared #176 MERGED 2026-05-16T20:46:32Z — Dependabot grooming batch report. Subagent task (Agent tool with isolation: worktree). 3 PRs in scope: pw-api #184 (minor-and-patch group, 5 updates) → auto-merge ENABLED 20:44:16Z; pw-website #36 (@vitejs/plugin-react 5→6, MAJOR) → recommend CLOSE (Astro 6 pins Vite 7; plugin 6 requires Vite 8 + removes Babel — upstream blocker); pw-website #53 (typescript 5→6, MAJOR) → recommend DEFER (single mechanical tsconfig fix: add "ignoreDeprecations": "6.0"). Subagent operator-touchpoint: 1 message (post-completion summary review). Report at dispatch/cli1/done/2026-05-16-164449-dependabot-grooming-batch.md.

  • pw-website #106 MERGED 2026-05-16T21:29:38Z — tsconfig.json ignoreDeprecations: "6.0". Orchestrator-direct 1-commit mechanical fix per operator pick; suppresses baseUrl deprecation warning that TypeScript 6.x would emit, unblocking Dependabot PR #53. Per memory orchestrator-direct-threshold: trivial 1-line config edit qualifies as orchestrator-direct (dispatch overhead ≈ work itself).

  • pw-website #53 MERGED 2026-05-16T21:32:50Z — typescript 5.9.3 → 6.0.3 (Dependabot major). Operator-approved DEFER-CLOSED path: tsconfig fix landed at #106@dependabot rebase → CI green after rebase (test 22.x SUCCESS) → auto-merge fired immediately on green. Total cycle: ~3 min wall-clock from rebase trigger to merge.

  • pw-website #36 CLOSED 2026-05-16T21:21 — @vitejs/plugin-react 5→6 (Dependabot major). Operator-approved CLOSE path per upstream dependency-graph conflict (Astro 6 pins Vite 7; plugin 6 requires Vite 8 + removes Babel). Dependabot will re-propose when Astro picks up Vite 8. Closing rationale in PR comment cross-references batch analysis at dispatch/cli1/done/2026-05-16-164449-dependabot-grooming-batch.md.

  • pw-api #184 — Dependabot minor/patch group (auto-merge enabled). 8 CI checks all SUCCESS; autoMergeRequest enabled 20:44:16Z by subagent; mergeStateStatus: BEHIND. Will merge once Dependabot rebases against the current main.

  • Worktree + branch hygiene sweep (orchestrator-direct, ~10 min). 15 locked subagent worktrees under .claude/worktrees/agent-* (from the prior iteration's 11-subagent total) → all unlocked + removed via git worktree remove --force. 47 local branches → deleted (32 squash-merged + 15 ancestor-merged; all safe — none had open PRs per memory orphan-cleanup-must-exclude-open-pr-branches). 1 scratch branch (chore/scratch-2026-05-16-eod, identical to main) → deleted. Final local state: 1 worktree (repo root on main), 1 branch (main). 22 remote orphan branches remain (operator-authorized cleanup per memory classifier-denial-on-bulk-destructive; deferred). Briefing's shared/ auto-merge GraphQL anomaly diagnosed: gh api repos/Protocol-Wealth/pw-shared/branches/main/protection returns 404 "Branch not protected" — explains noisy GraphQL error on --auto; verify via state,mergedAt not autoMergeRequest; memory artifact shared-no-branch-protection.md codifies.

  • Defense-in-depth model post-PR-Z4 (4 layers in production for outbound LLM payloads from pw-os-v2 — the production SDK call surface). Layer 1 (NEW; structural): PR-Z4 prompt-construction middleware at apps/api/src/lib/claude-client.ts; module-level wrap; ESLint custom rule blocks bypass. Layer 2: pii.ts Contract #3 in-band runtime detection (cross-repo byte-identical with pw-api/pw-portal-v2). Layer 3: Independent PII egress canary (2026-05-14; byte-identical pattern set re-implemented in pw-os-v2 + pw-portal-v2). Layer 4: outbound email NPI guard at pw-api Postmark send path (PR-D1; for the email-egress class). The first-layer structural guard now exists at the production SDK site; pw-api PR-Z3 remains structural-only (pw-api still makes ZERO Anthropic calls today). pw-portal-v2 PR-Z4-portal sibling port (1 SDK egress site there) sequenced as natural follow-on; deferred to capacity-permitting.

  • Subagent execution pattern at scale. 1 Dependabot grooming subagent this iteration (small scope). Cumulative across this day's full 4-window iteration + this same-day continuation: 12 subagents total (10 clean + 1 fix-forward + 1 Dependabot grooming). Worktree-isolation pattern via Agent tool isolation: worktree continues to scale cleanly for shared/-doc work; explicit git worktree add continues to be required for code-repo subagents per CLAUDE.md §6 single-agent-per-repo discipline.

  • Decision 4 (P2.7 sub-task 3) CLEARED + Decision 1 FIRED + Decision 2 SHIPPED + Decision 3 HOLD LIFTED — all four 2026-05-17 cycle decisions are now terminal. Captured in strategy/dispatch-next-iteration-queue.md Iteration handoff section with PR cross-references.

  • Carry-forward for next iteration: Wealthbox inbound sync (natural follow-on to make PR #221's skeleton useful — adds inbound GET /v1/contacts/<id>/custom_fields parsing path → writes to discrete columns + emits WEALTHBOX_NPI_BACKFILL_MALFORMED audit events on cast failure; the skeleton's audit-action constant is already in place); PR-Z4-portal (pw-portal-v2 sibling port; 1 SDK egress site; smaller scope than PR-Z4); PWOS subagent Phase 0 PoC (substrate validated; cash flow projection deliverable; ~2-3 worker-cycles); Phase 2 worker-side polling loop (now operator-gated next); 22 shared/ remote orphan branches batch cleanup (per-action operator authorization required); pw-website pending major bumps batch cleanup (#36 closed this iteration; future Vite-8-compatible re-propose).


2026-05-16 (final-anchor close — pw-website #105 + shared #161 CCO-gated landings + PR-D1 fix-forward + Decision 1 flip-to-default-on + ritual #17)

Iteration final close. Three CCO/CTO compliance-gated landings + PR-D1 fix-forward + Decision 1 polling-loop v2 flip-to-default-on fired + ritual #17 codified (Phase 6 archive must verify CI success, not just auto-merge enabled).

  • pw-website #105 MERGED 2026-05-16T18:45:12Z — first firm-published OS-licensing pitch surface (/os-licensing). CCO Marketing Rule §206(4)-1 gate cleared. Subagent C from the morning batch; ~3h DRAFT → MERGED wall-clock. Code at pw-website/src/pages/os-licensing.astro (527 lines); pulls Section 1 vocabulary + Section 2 capability table from credential-layer-positioning.md + Origin/PW/Verapath positioning from competitive-positioning-2026-05-16.md + OSS-wrapper architectural pattern from PWOS.md §15.1.2. Mirrors security.astro tone + disclosure discipline; SEC RIA #335298 + ADV pointer + third-party-name disclosure in footer; Calendly CTA ct3f-pwd-qgm/protocol-wealth-team-discovery. Zero performance claims, zero testimonials.

  • shared #161 MERGED 2026-05-16T18:47:00Z — ADR-PII-tagging §6 amendment with CCO + CTO double-gate cleared. Second ADR with that gate shape (PR #143 was the original; this is the first amendment). Amendment codifies that the middleware contract applies to every repo hosting an LLM SDK call surface, not just pw-api. Names pw-os-v2/apps/api/src/lib/claude-client.ts as the production SDK site requiring PR-Z4 port. Frontmatter revision-2: 2026-05-16; gates-downstream extended with PR-Z4.

  • pw-api PR-D1 #220 — outbound email NPI guard. cli1 dispatch (third polling-loop v2 lifecycle: v2 dispatch in the controlled rollout). Operator picked (1d) audit-wrapper-layer chokepoint at email-audit.ts instead of orchestrator-recommended (1a) PostmarkClient.send — smarter shape; wraps the existing email-audit pattern rather than bolting onto raw client methods. cli1 built: new src/middleware/email-npi-guard.ts (interceptEmailSend / interceptEmailSendTemplate + EmailGuardContext / EmailGuardResult types + EmailNpiBlockedError class) + 3 new audit-action constants (EMAIL_SEND_BLOCKED / EMAIL_SEND_AUDITED / EMAIL_WAIVER_CONSUMED) + 1 new env var (PII_WAIVER_SECRET) + 3 ESLint selectors + tests.

  • PR-D1 CI failure + orchestrator-direct fix-forward. cli1 archived PR-D1 as completed at 18:54:55Z when autoMergeRequest was non-null but CI was still in progress; Migration dry-run FAILURE landed 50 seconds earlier (18:53:41Z; not seen in cli1's poll). 3 pre-existing integration tests in email-audit.spec.ts failed because their fixtures didn't pass pii_tags and the new middleware correctly blocked the sends per Phase 1.5 pick 5 (default-pii.high for untagged). Orchestrator-direct fix-forward: pass explicit pii_tags to each failing test fixture preserving original test intent (each test still asserts the Postmark flow it was originally testing); push commit 6526608 to existing PR-D1 branch (no new PR); CI re-runs; auto-merge fires on green.

  • shared PR ritual #17 codification — Phase 6 archive must verify CI success, not just auto-merge enabled (autoMergeRequest non-null OR state == MERGED is insufficient when CI is still in progress). Updates worker pasteable block Step 4 with explicit poll-until-CI-terminal pattern + 10-minute poll-window cap; archives as failed-ci on any FAILURE conclusion; cross-references ritual #3 (enable-step verification — companion not replacement) + ritual #9 (classifier denial — one specific outcome failure mode). Cli1's 2026-05-16T18:54:55Z misfiled archive is the incident anchor. Ritual count delta: 16 → 17.

  • Decision 1 polling-loop v2 lifecycle: v2 flip-to-default-on FIRED. 3 clean lifecycle: v2 opt-in dispatches completed: PR-Z2 (pw-api #218 MERGED 16:57:49Z) + PR-Z3 (pw-api #219 MERGED 17:36:46Z) + PR-D1 (pw-api #220 MERGED post-fix-forward). Substrate validated end-to-end. Future iterations default to lifecycle: v2 without per-dispatch opt-in flag. Memory artifact lifecycle-v2-rollout-3-of-3-pattern.md captures the substrate validation evidence.

  • Defense-in-depth model post-PR-D1 (4 layers in production for outbound LLM + outbound email paths). Layer 1: PR-Z3 prompt-construction middleware in pw-api (structural; ESLint-guarded; pw-api makes ZERO current Anthropic calls so this is structural guard for future). Layer 2: pii.ts Contract #3 in-band runtime detection (cross-repo byte-identical). Layer 3: Independent PII egress canary in pw-os-v2 + pw-portal-v2 (egress-time backstop; deliberately re-implemented). Layer 4: PR-D1 outbound email NPI guard at pw-api Postmark send path (audit-wrapper-layer chokepoint). Plus pending PR-Z4 (pw-os-v2 middleware port; ADR §6 amendment unblocked it; queued as first work item of next iteration) closes the gap that pw-os-v2 (the production SDK call site) currently lacks the first-layer structural guard.

  • Iteration totals (2026-05-16 full day). ~30 PRs landed across the day across 4 windows: (1) morning iteration close (#141#150 + pwos-core #31; Phase 0.5 spike substantively closed); (2) late-day extension (#152#155 + pw-os-v2 #335 + DRAFT #105; 4 parallel subagents); (3) later late-day extension (#157#160 + pw-api #218 + pw-api #219 + #161 DRAFT + #162 + #163 + #164 + #165; 3 parallel subagents + cli1 PR-Z2 + cli2 PR-Z3 + orchestrator PR-D1 staging); (4) final-anchor close (#166 + this PR + ritual #17 + pw-api PR-D1 fix-forward + #105 + #161 CCO-approved). 11 subagents total across the day (10 clean + 1 fix-forward); ritual #15 worktree-locked-main fallback fired 12+ times (self-validated at scale; ritual #16 + #17 codified from incidents within the same day they were observed).

  • Operator state-hygiene observations carrying forward. (a) Bulk orphan-branch cleanup must exclude open-PR branches (especially DRAFT) — codified as orphan-cleanup-must-exclude-open-pr-branches.md after PR #161 DRAFT was accidentally closed by cleanup, recovered via gh api -X POST refs + gh pr reopen. (b) Operator-specific Phase 1.5 picks deviated from orchestrator pre-stage once this day (PR-D1 pick 1: (1d) audit-wrapper-layer chokepoint instead of (1a) PostmarkClient.send) — workers and operators extending the orchestrator's pre-staged picks at diagnostic time is a healthy substrate pattern.

  • Next-iteration-briefing.md refresh anchors: PR-Z4 (pw-os-v2 middleware port) as first work item against validated lifecycle: v2 default-on substrate; Wealthbox NPI promotion second priority (cli2; scoping doc at strategy/wealthbox-npi-fields-promotion-plan.md per PR #163); lifecycle: v2 default-on flip codification as orchestrator-direct mechanical work (dispatch/protocol/lifecycle.md + worker-launch-ritual.md + orchestrator-daily-start-prompt.md count + flag references).


2026-05-16 (later late-day — Stream Z PR-Z2 + PR-Z3 land in pw-api + Stream D PR-D1 staged + ADR §6 DRAFT + ritual #16 + Wealthbox NPI scoping)

Second operator-extension window in the same day. cli1 (PR-Z2) + cli2 (PR-Z3) ran sequentially; both landed clean in pw-api with lifecycle: v2 substrate. PR-D1 (Stream D outbound email NPI guard) drafted + staged for cli1 — third dispatch of the Decision 1 controlled rollout. 3 subagents (A/B/C) spawned in parallel during the wait/drafting window. Decision 1 flip-to-default-on threshold met: 3 clean lifecycle: v2 dispatches (PR-Z2 + PR-Z3 + PR-D1 staging completing); future iterations default to lifecycle: v2 without per-dispatch opt-in flag.

  • pw-api #218 — PR-Z2 pii_tags column on client-data ingestion tables (94125c4, MERGED 2026-05-16T16:57:49Z). cli1 dispatch; first polling-loop v2 lifecycle: v2 opt-in dispatch. JSONB per-field shape (pii_tags JSONB NOT NULL DEFAULT '{}'); 11 tables migrated (clients, households, external_identities, quiltt_profiles, quiltt_connections, accounts, quiltt_transactions, altruist_households, altruist_accounts, client_assessments, client_wallets); 13 INSERT call sites updated; 68 backfill JSONB keys; canonical typed map at src/lib/pii-tags.ts. Test baseline exact (0 delta). 6 CI checks all SUCCESS. Phase 1.5 picks: JSONB shape + CTO-only gate + explicit backfill to pii.high per-text-column + Wealthbox custom NPI fields tagged as clients.metadata pii.high catch-all (future PR promotes to discrete columns per the scoping doc landed below).

  • pw-api #219 — PR-Z3 prompt-construction exclusion middleware (MERGED 2026-05-16T17:36:46Z). cli2 dispatch; second lifecycle: v2 opt-in dispatch. New src/middleware/pii-prompt-construction.ts (~350 lines) exporting excludeHighPii / excludeHighPiiByTable / verifyWaiver / memoryJtiStore / redisJtiStore; 5 ADR §6 canonical event names added to src/lib/audit-actions.ts (PII_FIELD_EXCLUDED / PII_WAIVER_GRANTED / PII_WAIVER_CONSUMED / PII_WAIVER_REJECTED / PII_MEDIUM_INCLUDED); PII_WAIVER_SECRET env var declared (operator provisions GCP Secret Manager out-of-band); ESLint no-restricted-syntax extended with 3 selectors blocking direct @anthropic-ai/sdk / openai imports outside the middleware module; 18 new tests; cli2 invented the ESLint sentinel validation pattern at Phase 4 (later codified as ritual #16 below). cli2 added a 5th Phase 1.5 pick (untagged-payload posture) beyond the orchestrator's pre-stage — workers extending the pick set at diagnostic time is a healthy substrate pattern. pw-api has ZERO Anthropic SDK call sites today (consistent with CURRENT-STATE.md AI surface anchor); middleware ships as structural guard for future LLM client additions in pw-api.

  • shared #157 — cli1 PR-Z2 task completed; archived (1ed8add, MERGED). 83-line completion archive at dispatch/cli1/done/2026-05-16-165906-completed.md capturing 4 Phase 1.5 picks + 11-table substitution count + Phase 4 verification + lifecycle: v2 substrate observations. Archived via ritual #15 worktree-locked-main fallback (cli1's git checkout main failed; routed via branch + PR + auto-merge).

  • shared PRs #158 + #159 — PR-Z3 activation to cli2 + cleanup of stale cli1 pending.md (79d8124 + 0fcb974, both MERGED). Operator git mv'd dispatch/shared/pr-z3-staged-dispatch.md to dispatch/cli2/pending.md + paste-launched cli2; followed by orchestrator-direct cleanup of cli1's stale pending.md (a carry-over from the worktree-isolated archive pattern that couldn't span worktrees for atomic git mv).

  • shared #160 — cli2 PR-Z3 task completed; archived (632c756, MERGED). Completion archive at dispatch/cli2/done/2026-05-16-XXXXXX-completed.md; ritual #15 fallback fired again for the archive PR.

  • shared #161 (DRAFT) — ADR-PII-tagging §6 amendment naming pw-os-v2 claude-client as parallel implementation site (subagent A; isDraft=true; awaiting CCO). Codifies that the middleware contract applies to every repo hosting an LLM SDK call surface, not just pw-api. PR-Z3 shipped to pw-api as structural guard; pw-os-v2's apps/api/src/lib/claude-client.ts is the actual production SDK site (per the 2026-05-16 subagent D scope correction in pw-os-v2 #335). Frontmatter revision-2: 2026-05-16; gates-downstream extended with PR-Z4 (pw-os-v2 middleware port). Explicit CCO Marketing Rule + Reg S-P §248.30(b) safeguards-rule gate in PR body.

  • shared #162 — ritual #16 ESLint sentinel validation pattern (94d80d6, MERGED). Codifies cli2's PR-Z3 Phase 4 invention: when adding restrictive ESLint rules, add a sentinel file with the forbidden pattern + a paired sentinel in an allowlist path; run lint to verify both fire/don't-fire as expected; delete sentinels; re-lint clean; THEN commit. Catches silent-selector-typo and allowlist-scope-too-broad failure modes. Cross-references ritual #3 (auto-merge enable + verify) as companion verification-discipline pattern. Ritual count delta: 15 → 16; updated in worker-launch-ritual.md + next-iteration-briefing.md + orchestrator-daily-start-prompt.md + dispatch-next-iteration-queue.md.

  • shared #163 — Wealthbox NPI fields promotion-from-metadata plan (b9023a1, MERGED). 197-line scoping doc at strategy/wealthbox-npi-fields-promotion-plan.md. Pre-stages tomorrow's cli2 dispatch on promoting the 6 ADR §4 Wealthbox custom NPI fields from clients.metadata JSONB catch-all to discrete typed columns. 4 fields promoted to discrete columns (tax_filing_status / dependents_count / irs_backup_withholding / control_person / finra_affiliation); Telegram handle stays in metadata. Copy-paste-ready migration SQL + Wealthbox API field-mapping + cast-with-fallback backfill (10% malformed-cast threshold sentinel) + Phase 1.5 picks pre-staged for cli2.

  • shared #164 — PR-D1 dispatch staging to cli1 (5df8fce, MERGED). Stream D PR-D1 implementation per credential-layer-positioning.md §3 Gap 3 (outbound email NPI guard / PII-aware retrieval). Consumes PR-Z3 middleware; interceptor candidates at pw-api/src/lib/email.ts PostmarkClient class; Phase 1.5 SURFACE pre-stages 5 picks (interceptor location / fail-closed posture / audit event names matching /^[a-z]+(\.[a-z_]+){1,2}$/ regex / template merge-field handling / untagged-payload posture matching PR-Z3). Auto-merge fired clean.

  • Decision 1 lifecycle: v2 rollout 3-of-3 readiness signal. PR-Z2 (#218) + PR-Z3 (#219) + PR-D1 (staged) completes the controlled 3-dispatch rollout. PR-D1 must complete to fully trigger the flip; the staging itself is the third dispatch's birth event. Per saved memory artifact lifecycle-v2-rollout-3-of-3-pattern.md: substrate validated (atomic state.md writes; clean Phase 1.5 SURFACE behavior; ritual #8 STOP discipline honored across all 3); future iterations default to lifecycle: v2 without per-dispatch opt-in.

  • Defense-in-depth model post-PR-Z3 (4 layers). Layer 1: PR-Z3 prompt-construction middleware in pw-api (structural; ESLint-guarded). Layer 2: src/lib/pii.ts Contract #3 in-band runtime detection (cross-repo byte-identical). Layer 3: Independent PII egress canary in pw-os-v2 + pw-portal-v2 (egress-time backstop; deliberately re-implemented). Layer 4 (in flight via PR-D1): outbound email NPI guard at pw-api Postmark send path. Plus pending PR-Z4 (pw-os-v2 middleware port) closes the gap that pw-os-v2 (the production SDK site) currently lacks the first-layer structural guard.

  • Ritual #15 worktree-locked-main fallback 5+ reproductions in this batch. Every shared/ archive PR (subagent A, subagent B, subagent C, PR-D1 staging, cli1 archive, cli2 archive) hit the worktree-locked-main case during gh pr merge --delete-branch; retry without --delete-branch succeeded each time; orphan remote branches cleaned via gh api -X DELETE post-merge. The pattern has now self-validated 8+ times since codification at PR #153 (2026-05-16 morning). Polling-loop v2 Phase 2 per-worker git worktrees eliminates this structurally; until then, it's the multi-subagent-iteration environmental constant.

  • Subagent worktree-isolation at scale (second batch this iteration). 3 subagents (A: ADR §6 amendment DRAFT; B: ritual #16 codification; C: Wealthbox NPI scoping doc) spawned in parallel via Agent tool with isolation: worktree on shared/. All 3 landed cleanly within ~5 minutes wall-clock from spawn; A held in DRAFT per directive, B + C auto-merged. Combined with first-batch 4 subagents earlier in the day (total 7 subagents this iteration; 6 clean + 1 DRAFT awaiting CCO). The 6-subagent 2026-05-16 batch + 7-subagent 2026-05-16-later batch validate worktree-isolation as the canonical multi-subagent shared/-work pattern at full production scale.


2026-05-16 (late-day — iteration extended with 4 parallel subagents + PR-Z2 dispatch + ritual #15 codification + hook tightening + pw-os-v2 multi-user identity fix)

Operator extended the 2026-05-16 iteration past the briefing-refresh anchor (#151) for a ~1h autonomous-orchestrator push-out window. 5 PRs landed within ~25 min wall-clock (#152 housekeeping at 15:25Z → pw-os-v2 #335 at 15:45Z); 1 DRAFT opened (pw-website #105 awaiting CCO).

  • #152 (chore/iteration-close-housekeeping) — archive dispatch/cli2/post-compact-observation-2026-05-16T14-25-00Z.md + resume-observation-2026-05-15-195700.md to per-spike archive/2026-05-{15,16}-{tier1,tier2}-snapshots/ directories per spike-findings §8 citation conventions.

  • #153 (subagent A; ritual #15 codification)dispatch/shared/worker-launch-ritual.md adds hardening item #15: when main is locked by another worktree (e.g., agent-tool worktree at .claude/worktrees/agent-*), route archive commits via branch + PR + auto-merge instead of direct push. Aligns with CLAUDE.md §1.4 PR-not-direct-push preference; worktree-locked-main case promotes the preference to a hard requirement. Worker pasteable block Step 3 updated with inline fallback note. Ironic self-validation: the codification PR's own gh pr merge --delete-branch hit the exact failure mode being codified; retry without --delete-branch succeeded. Reproduced 3× in 10 min across PR #153 + #154 + #155.

  • #154 (subagent B; security_reminder_hook path-allowlist tightening) — new doc at strategy/security-reminder-hook-tightening.md. Investigation found the hook is plugin-managed at ~/.claude/plugins/marketplaces/claude-plugins-official/plugins/security-guidance/hooks/; direct git-tracked tightening not feasible (plugin-source read-only). PR-able surface is the documentation entry capturing recommended user-side ~/.claude/settings.json wrapper hook + 3-iteration false-positive history (CHANGELOG #11/§102/§113; cli2 archive at done/2026-05-16-104419-completed.md:284; tier2-snapshot at archive/2026-05-16-tier2-compaction-snapshots/d2-post-compact-observation.md:63-66). Tightening shape scoped specifically to concurrency-only field edits on .github/workflows/*.yml (concurrency.group uses GitHub-controlled context vars; not the run: injection surface).

  • #155 (orchestrator; queue-file refresh + cli1 PR-Z2 dispatch)strategy/dispatch-next-iteration-queue.md refreshed from 2026-05-15 anchor to 2026-05-16 close (Queue A: Tier 1 attempt-4 DE-QUEUED + Tier 2 COMPLETED + Phase 1 lifecycle.md authored + adoption work active; Queue B: 2026-05-17 dispatches priority-ordered; Decisions 1-4 codified). dispatch/cli1/pending.md written with first polling-loop v2 lifecycle: v2 opt-in dispatch (Decision 1 — opt-in flag on first ~3 dispatches; flip default-on after 3 clean). PR-Z2 dispatch: pii_tag column on client-data ingestion tables per ADR-PII-tagging.md (PR #143); Phase 1.5 SURFACE pre-staged with 4 picks (table scope / column shape / backfill posture / CTO+CCO concurrent gate question). PR-Z3 (cli2) staged at dispatch/shared/pr-z3-staged-dispatch.md per Decision 2 sequencing.

  • pw-os-v2 #335 (subagent D; PWOS multi-user identity fix) — fixes a hardcoded two-name team-reference string in PWOS chat agent's system prompt. Scope correction: the hardcoded string lived at pw-os-v2/apps/api/src/lib/claude-client.ts:163, NOT in pw-api as initially scoped — pw-os-v2's apps/api is the chat-mediating server; pw-api makes ZERO Anthropic calls (CURRENT-STATE.md anchor). Fix: new apps/api/src/lib/partners.ts with getTeamReference(userEmail) helper reading from canonical PARTNERS roster; excludes current user; converts DEFAULT_SYSTEM_PROMPT to template with {{TEAM_REFERENCE}} token; both /messages create + regenerate routes templated per-request. Test fixture covers 3 principal-to-team-reference mappings (each principal's prompt names the other two partners) + 4 fallback cases (unknown email / null / undefined / empty string → "your partners"). 1113 tests pass; auto-merge fired immediately on all 6 CI checks green (TypeScript / Tests / ESLint / Build / Security / Compliance lint).

  • pw-website #105 (subagent C; OS-licensing pitch surface — DRAFT) — 527-line src/pages/os-licensing.astro mirroring src/pages/security.astro tone + disclosure discipline. Pulls Section 1 vocabulary + Section 2 capability table from shared/strategy/credential-layer-positioning.md; Origin/PW/Verapath positioning from shared/strategy/competitive-positioning-2026-05-16.md; OSS-wrapper architectural pattern from shared/architecture/PWOS.md §15.1.2; Calendly CTA ct3f-pwd-qgm/protocol-wealth-team-discovery. noindex while review is pending; in-page DRAFT banner; SEC RIA #335298 + ADV pointer + third-party-name disclosure + capability-status-honesty note in footer. Zero performance claims, zero testimonials, zero superlatives. CCO Marketing Rule §206(4)-1 review gate explicit in PR body; auto-merge NOT enabled per operator directive. Missing-CCO-memo-v2-supplement noted in PR body.

  • Subagent worktree-isolation at scale validated again — 4 subagents spawned in parallel via Agent tool with isolation=worktree (shared/) for A + B; explicit git worktree add /tmp/pw-os-v2-subagent-d-multi-user for D in pw-os-v2; C operated in pw-website without isolation needed. All 4 landed cleanly; ~25 min wall-clock from spawn to last-merge. Codified as canonical pattern in memory artifact subagent-worktree-isolation-canonical.md. 2026-05-16 ran 6 subagents (5 clean, 1 chat-surface review for PR #141); 2026-05-17 open ran 4 (3 clean + 1 DRAFT per directive).

  • 3 orphan remote branches cleaned post-mergegh api -X DELETE refs/heads/chore/{ritual-hardening-15-worktree-locked-main-fallback,security-reminder-hook-path-allowlist-investigation,iter-2026-05-17-open} — all three PRs hit the ritual #15 worktree-locked-main case during gh pr merge --delete-branch; remote refs persisted post-squash-merge. Local prune also removed 5 stale orphan refs from prior iterations.

  • Operator-approved Decisions 1-4 codified in queue-file — Decision 1 (Phase 1 lifecycle: v2 opt-in on first ~3 dispatches); Decision 2 (Stream Z Z2 first to cli1, Z3 auto-dispatch to cli2 after Z2 lands); Decision 3 (PWOS Phase 0 PoC hold one cycle until v2 substrate validates with 3 clean dispatches); Decision 4 (P2.7 sub-task 3 subagent-DRAFT only; CCO Marketing Rule §206(4)-1 gate on publish). PR-Z2 dispatch is rollout dispatch #1; subagents A-D occupied the iteration's parallel-execution capacity.


2026-05-16 (orchestrator iteration close — 10 shared/ PRs + pwos-core #31; Phase 0.5 spike substantively closed; first CCO ADR gate cleared; PWOS positioning sharpened)

  • Three foundational themes anchor this iteration. (1) Phase 0.5 polling-loop v2 compaction spike substantively closed — Tier 2 actual-compaction (pwos-core target via PR #147 / pwos-core #31) produced a clean recovery-clean outcome with high-fidelity d2 recall + d4 self-aware compaction narration + protocol-adherent disk re-read discipline; §9 recommendation flipped provisional → empirically confirmed (option b checkpoint-based-resume). Phase 1 lifecycle.md (#142) was authored against option (b); design holds. Tier 1 attempt-4 obviated. (2) First CCO ADR gate cleared — Stream Z PR-Z1 PII-tagging ADR (#143) shipped with explicit CTO+CCO sign-off, validating the CCO ADR-review flow as a production pattern; unblocks Stream Z PR-Z2 + PR-Z3 (both pw-api worker dispatches). (3) Strategic positioning sharpened — OSS-wrapper pattern operationalized in PWOS.md §15.1.2 (#141); ROADMAP gained P2.12 PWOS multi-agent orchestration (#145); P2.6 commercial-precedent register expanded to 5 names (Supabase, Tailscale, dbt Labs, Anthropic added alongside MongoDB/Red Hat); Origin/Verapath/PW competitive landscape analyzed at strategy/competitive-positioning-2026-05-16.md (#146); PW positioning sweet spot is the B2A2C OS-licensing tier between Origin (autonomous-consumer extreme) and Verapath (on-prem enterprise extreme).

  • pw-shared #141 — PWOS.md §15.1.2 OSS-wrapper fold-in (51916e5, merged 14:48:56Z). Structural firm-state edit; operator approved via chat-surface 5-line summary (no PR-file open); first instance of the chat-surface-review pattern codified as carryforward learning. Sub-section frames OS-licensing thesis around the Supabase/Tailscale/dbt-Labs commercial-precedent set.

  • pw-shared #142 — Polling-loop v2 Phase 1 worker lifecycle protocol (e23a0a5, merged 14:00:20Z). Authoring at shared/dispatch/protocol/lifecycle.md (456 lines); checkpoint-based-resume substrate per Phase 0.5 §9 provisional option (b). Phase 1 design holds post-Tier-2 empirical confirmation later in the iteration.

  • pw-shared #143 — Stream Z PR-Z1 PII-tagging ADR (0a3e1c6, merged 14:52:46Z). 192 lines at architecture/decisions/ADR-PII-tagging.md; pii.{high,medium,low} taxonomy + exclusion-middleware shape; CTO + CCO double-gate cleared (first ADR with explicit CCO gate landing). Unblocks PR-Z2 (pii_tag column on client-data ingestion tables; pw-api worker dispatch) + PR-Z3 (prompt-construction exclusion middleware; pw-api worker dispatch).

  • pw-shared #144 — PWOS multi-agent architecture sketch (b94e410, merged 14:02:25Z). 418 lines at architecture/PWOS-multi-agent-sketch.md; Conductor + deliverable-subagents shape (retirement analysis, financial plan, cash flow projection, proposal generation) with scoped Wealthbox/Quiltt/Mercury read access + Word/PDF/Google Docs output via existing pandoc + xelatex toolchain. Detailed-enough to dispatch PWOS Phase 0 PoC against; sketch supersedes prior pwos.app/workstreams informal capture.

  • pw-shared #145 — ROADMAP P2.12 PWOS multi-agent orchestration + P2.6 OSS-wrapper precedents (89980ba, merged 14:04:50Z). P2.12 added as the productized-multi-agent vector; P2.6 commercial-precedent register expanded from 2 names (MongoDB, Red Hat) to 5 (added Supabase, Tailscale, dbt Labs, Anthropic) — each ships an open-source core + builds commercial value via hosted infra / paid features / proprietary extensions, mirroring the PWOS licensing thesis.

  • pw-shared #146 — Competitive positioning analysis (Origin + Verapath + PW) (b8de596, merged 14:07:32Z). 385 lines at strategy/competitive-positioning-2026-05-16.md. Anchors PW between Origin Financial (autonomous-non-discretionary consumer extreme — speed + scale but no fiduciary HITL) and Verapath (on-prem enterprise extreme — sovereignty but high friction); PW occupies the B2A2C OS-licensing sweet spot via CCO HITL Tier 2 review + multi-RIA productization vector. Canonical comparison anchor for OS-licensing pitch + investor briefings.

  • pw-shared #147 — cli2 Tier 2 dispatch + BlockSkunk Phase 0/1 deliverable + COMPLIANCE-CHANGELOG entry (89f224c, merged 14:09:09Z). 4 files / 1 PR bundling pattern: (a) dispatch/cli2/pending.md for Tier 2 actual-compaction spike vehicle dispatch (target: pwos-core concurrency-block on 2 workflows); (b) compliance/audits/blockskunk-phase-0-1-deliverable.pdf landed (unblocks Reg S-P Compliance Record §4 finalization per ROADMAP P0.7); (c) COMPLIANCE-CHANGELOG entry per Rule 204-2 audit-trail discipline. Operator-touchpoint cost: 1 PR, not 3 — codifies bundling pattern for routine doc maintenance.

  • pwos-core #31 — Tier 2 compaction spike vehicle (cf6df62, merged 14:41:22Z). ci: add concurrency block to 2 workflows — added concurrency: cancel-in-progress to .github/workflows/license-compliance.yml + .github/workflows/spdx-headers.yml. Meaningful infra hygiene (CI workflow-run dedup) independent of spike outcome. cli2 dispatched against this target, completed Phase 2, narrated awaiting: compact, operator fired /compact, cli2 resumed cleanly post-compact, finished Phase 5+6, PR merged. Tier 2 outcome: recovery-clean empirical signal — d2 recall preserved pre-compact state across compaction boundary; d4 worker self-narrated the compaction event in observation file; protocol-adherent disk re-read (no working from memory).

  • pw-shared #148 — P2.7 reframe + new P3.11 + PWOS-multi-agent-sketch §4.5 Verapath fold-in (502efce, merged 14:16:04Z). P2.7 reframed as complementary-not-competitive vs Verapath (different deployment tier); new P3.11 codifies CCO HITL Tier 2 review as the PW differentiator vs Origin autonomous-non-discretionary posture; §4.5 added to PWOS-multi-agent-sketch.md with explicit Verapath comparison context for Phase 0 PoC design.

  • pw-shared #149 — cli2 Tier 2 archive (2ea7191, merged 14:44:47Z). dispatch/cli2/done/2026-05-16-104419-completed.md (full Tier 2 completion report) + d2 post-compact-observation-2026-05-16T14-25-00Z.md (pre-disk-read recall capture; high-fidelity per spike protocol).

  • pw-shared #150 — Spike findings §8.2 empirical + §9 confirmed (19d839f, merged 14:53:25Z). Tier 2 results synthesized: clean recovery within full test scope (not narrowed-scope like Tier 1 attempt-3). §9 recommendation: option (b) checkpoint-based-resume empirically confirmed for the original spike's primary question (mid-edit recovery via on-disk artifacts post-compaction event). Phase 1 lifecycle.md (#142) holds; Tier 1 attempt-4 DE-QUEUED.

  • State-narration awaiting: compact enum extension landed as substrate for Tier 2 — first iteration extending the polling-loop v2 Phase 2 precursor (originally engineered for awaiting: kill in Tier 1 attempt-3) to the compaction lifecycle event. Substrate worked perfectly first-try; no architectural reshuffle required. Banks toward Phase 2 generalization to every phase boundary + heartbeat-staleness detection.

  • Subagent worktree-isolation pattern validated at scale. 6 subagents spawned across this iteration in isolated worktrees prevented .git/index race entirely; 5 landed clean PRs autonomously, 1 surfaced for operator review (PR #141 chat-surface review pattern). Codifies worktree-isolation as the canonical multi-subagent pattern for shared/ doc work — distinct from cli{1,2} worker dispatches to code repos.

  • security_reminder_hook PreToolUse hook false-positive — third consecutive iteration fired on workflow Edit/Write (this iteration's pwos-core #31 mechanical workflow concurrency-block insertion). Promoted to hard-tracked review item; carry-forward to next-iteration operator-direct triage.

  • CTO/CISO + CCO sole approval. Stream Z PR-Z1 ADR carries explicit CTO + CCO sign-off (first instance). All other PRs are CTO/CISO-only routine doc maintenance per CLAUDE.md §1.4. No live compliance-content change requiring CCO review beyond the ADR gate.


2026-05-15 (orchestrator morning iteration close — ~12 PRs landed, duplicate-work detection codified, P2.7 sub-task 1 + Phase 0.5 spike plan + pw-website CI hardening)

  • Tight 2-hour focused iteration running ~10:00Z–12:15Z built on the prior ~03:30Z close. 12 PRs landed: shared/ #114, #115, #116, #117, #118, #119, #120 + 2 archive commits; pw-website #100 + #101 (operator-prompted CI work) + #95 (astro 6.1.10 dependabot auto-merge).

  • Credential-layer-positioning canonicalization (ROADMAP P2.7 sub-task 1) DONE via orchestrator-direct PR #118. Doc landed at shared/strategy/credential-layer-positioning.md (402 lines, byte-identical to operator's v0.3 desktop draft). 4 incoming refs updated (ROADMAP P2.7 + dispatch-next-iteration-queue Queue B + next-iteration-briefing 2 lines). Pre-canonicalization audit found zero PW/pw-os/shared/ body refs (frontmatter already canonical-shape). Sub-tasks 2 (PWOS.md §15.1 fold-in) + 3 (pw-website pitch surface) + 4 (Stream Z/A/B/C roadmap items) sequenced.

  • Ritual hardening #10 (Phase 0 already-landed pre-flight) codified via PR #116 — bundled with orchestrator-prompt step 0 + overnight-diagnostics §5 enumeration of recently-merged PRs. Driven by TWO duplicate-work findings this iteration: pw-website-weekend-hygiene + nexus-core-weekend-hygiene worktrees both showed unpushed local commits but the work had already landed via separate PRs (#98 + #18, both merged 2-4h before the overnight diagnostic ran). Two-layer defense (orchestrator-side step 0 + worker-side ritual #10) catches duplicates at either dispatch-authoring time OR worker Phase 0 boundary.

  • pw-website CI Node 20→22 bump via PR #100 (cli2 dispatch) unblocks astro@6 dependabot bumps + clears Node 20 EOL pressure. Sibling-repo alignment (pwos-core / pw-os-v2 / pw-api on 22.x+).

  • Auto-merge workflow hardening (pw-website #101) — orchestrator-direct after Step 2.2 investigation root-caused the devalue PR #97 30-min hang. Workflow now: (a) names which checks are still running (was: "1 checks still running" without identification); (b) 10-min phantom-check escape hatch surfaces named culprit if any check has started_at >10 min with status != "completed". Future hangs surface their cause + escape within 10 min instead of 30. No change to merge semantics.

  • Polling-loop v2 Phase 0.5 spike plan drafted via PR #119 (no auto-merge; operator-reviewed + merged 2026-05-15T12:04:47Z). Living document at shared/strategy/dispatch-compaction-spike-findings.md. Resolves: which compaction-recovery posture Phase 1's state machine implements — (a) FAILED_COMPACTION auto-archive + re-dispatch, or (b) checkpoint-based resume via in-progress.md frontmatter. Experimental tiers: 1 simulated (kill+restart fresh worker; cheap proxy), 2 actual compaction attempt (deliberately context-heavy task; up to 3 attempts), 3 API instrumentation (fallback). Empirical phase queued for next iteration.

  • Two cli dispatches landed cleanly: cli2 PR #115 (pw-website Node bump dispatch) → cli2 PR #100 (target work) → archive f3768a2. cli1 PR #114 (pw-website-weekend-hygiene push dispatch) → DUPLICATE-WORK SURFACE (target had merged as PR #98 ~3.5h before overnight diagnostic ran; cli1 pushed + opened PR #99, then operator-relayed finding caught it; #99 closed; cli1 archived surfaced-operator-decision-only at db8b457). Lessons fed into ritual #10 codification.

  • dispatch-next-iteration-queue refresh via PR #117 — was stale by 2 iterations; now anchors current state with cascade-close lookback + this iteration's in-flight + Queue A (polling-loop v2 unchanged) + new Queue B (Tier-1 strategic candidates per briefing Decision 1) + new Queue D (major-version dependabot bumps awaiting operator review).

  • Memory artifact added: orchestrator-direct-threshold.md captures operator guidance — trivial git/gh ops + simple text updates run orchestrator-direct; non-trivial code-affecting work dispatches to a worker. Calibration: if dispatch overhead ≈ work itself, run direct.

  • Operator-direct cleanups completed: orphan remote ref pw-website/docs/2026-05-15-weekend-hygiene-shared-refs deleted via gh api -X DELETE; cleanup-worktrees.sh --apply --force cleared both pw-website-weekend-hygiene + nexus-core-weekend-hygiene worktrees (both confirmed duplicate-of-landed; no work-loss).

  • Dependabot rebase queue (pw-website 8 → 6 open): @dependabot rebase triggered on all 8 PRs after #101's new diagnostic workflow shipped. #93 (minor-patch group) + #95 (astro 6.1.10) + #55 (astro 5→6 duplicate) cleared; #103 (new minor-patch group post-rebase) appeared. Remaining: 3 non-major (#71 postcss / #92 fast-uri / #97 devalue) processing through new workflow; 2 major (#36 vitejs / #53 typescript) sit for operator review. Note: #95 was a major bump that auto-merged unexpectedly — worth verifying classification + Tests-on-Node-22 next inspection.

  • cli1 follow-up dispatch queued via PR #120 — PWOS.md §15.1 credential-layer fold-in (P2.7 sub-task 2). Worker dispatched with 3 fold-in shape options (A: single bullet, B: two bullets, C: new §15.1.1 sub-section); mandatory Phase 1.5 SURFACE before applying; PR WITHOUT auto-merge per CLAUDE.md §1.4 (structural change to canonical firm-state doc). Operator launched cli1 ~12:10Z (mid-iteration); cli1 will SURFACE then STOP awaiting operator pick on shape since operator stepped away.


2026-05-15 (orchestrator iteration close — 25 PRs landed, doc-cleanup arc complete, multi-cycle protocol validation)

  • 25 PRs landed in this orchestrator iteration spanning shared/, pw-api, pw-os-v2, pw-portal-v2, pw-website, pw-infrastructure, pwos-core, nexus-core. The iteration opened with the cli1 + cli2 Phase 3 ALREADY-BROKEN cleanup (synthesized in the entry below) and progressed through three structural protocol improvements + Queue B/C/D doc-cleanup arc completion + classifier-layer permissions findings.

  • PR-not-direct-push convention codified for shared/ doc maintenance (shared #94): replaced the worker-launch-ritual's direct-push-to-main pattern with PR-with-auto-merge for routine doc writes. Codified shared/CLAUDE.md §1.4 + worker-launch-ritual Step 3 + cross-reference in multi-agent-coordination.md item 7. Codification driven by three live forcing moments — PR #88 (read-from-disk launch pattern PR'd not direct-pushed despite ritual saying direct push), classifier denials of orchestrator direct-push attempts, and the 83f346b incident where cli2's git add -A swept 15 lines of CURRENT-STATE.md + 29 lines of dispatch-next-iteration-queue.md orchestrator WIP into a worker archive commit.

  • Scoped git add ritual hardening (shared #94, same PR): Step 3 now uses git add dispatch/cliN/done/ instead of git add -A. Multi-cycle validated across every subsequent archive commit (cli1 + cli2, both repos): each archive stats as 1 file rename + N insertions, zero swept WIP.

  • Phase 1.5 STOP override-strong wording (shared #96 dispatch + carried forward): explicit per-dispatch language that dispatch-level Phase 1.5 SURFACE-TO-OPERATOR stops OVERRIDE the launch-prompt's global "work without stopping for clarifying questions" directive. Recursive validation arc: cli2 PR #332 deviation → PR #96 codification → PR #333 honored stop → PR #4 regression → PR #19 named-prior-bypass framing held. Per-dispatch wording is the iteration-stopgap; ritual-level codification queued for next-iteration hardening pass.

  • Auto-merge classifier permissions layering finding: ~/.claude/settings.json permissions.allow for Bash(gh pr merge *) was added to bypass classifier denials. Resolved 4-of-5 subsequent scenarios but cli1 PR #191 surfaced the remaining failure mode — classifier reasons over message-level intent ("'proceed with B' approved the rewrite, not self-merging"), not just command pattern. permissions.allow is at the standard prompt layer; classifier is a separate intent-evaluation layer (autoMode settings section). True structural fix is polling-loop v2's "worker proposes; orchestrator handles via API with explicit auth context" pattern. Worker dispatches now include "expect classifier may deny first attempt; if denied, surface immediately and await operator unblock" guidance.

  • Queue B (operator-decision content fixes) cleared 4-of-4 + bonus row: pw-os-v2 #332 (PW-SECURITY-POSTURE glob + cdf.md aspirational ref) + pw-infrastructure #188 (open-source-inventory + covered-ui-checklist legacy refs) + bonus pw-os-v2 #333 (Shared docs section coherent rewrite, surfaced by cli2 in #332). Plus PW-ARCHITECTURE-DEEP-DIVE archive divergence resolved: pw-shared #91 (dedupe to canonical archive/state/) + pw-api #216 (realign refs); pw-os-v2 already aligned.

  • Queue C (canonical-references.md rewrite) landed via option B pointer-replacement (pw-infrastructure #189; cli1 audit revealed 25 surfaced refs were actually 37 distinct doc citations: 52% fabricated / 36% wrong-pathed / 12% correct in pre-rewrite content). Followed by the 11-incoming-ref reconciliation (pw-infrastructure #190) + canonical-ops-section rewrite in pw-infrastructure CLAUDE.md (pw-infrastructure #191). Three-PR arc: replace canonical doc → reconcile incoming refs → trim adjacent stale section.

  • Queue D (weekend hygiene) — four bundles delivered: pw-website #98 (14 uppercase SHARED/ refs + 7 legacy absolute-path refs in GOVERNANCE-BASELINE.md retargeted; that doc relabeled as historical baseline); pwos-core #20 (README Fly.io qualified with PW Cloud Run note + dependabot.yml provisioned + 7 SPDX backfills); nexus-core #4 (pyproject.toml FMP comment fix + dependabot.yml provisioned + 20 __init__.py SPDX backfills); nexus-core #19 (CLAUDE.md authored from scratch using pwos-core/CLAUDE.md as sibling pattern).

  • Boomer / Kelp Financial deal structure dropped (per operator decision): ROADMAP P2.7 (revenue tracking for monthly Boomer disbursements) + P3.4 (Kelp Financial acquisition deal structure) + CURRENT-STATE Decision queue entry + CCO Friday-meeting prep mention all removed. Frees CCO capacity for Reg S-P / Reg XP June 3 deadline + DocuSign signing queue. Vault Summit (June 5), Open Cover insurance, Farmhouse attorney follow-up retained on CIO queue.

  • PWOS subagent productization concept surfaced for post-doc-cleanup horizon: extend the orchestrator multi-agent pattern into PWOS itself — main PWOS chat agent spawns deliverable subagents (retirement analysis, financial plan, cash flow projection, proposal generation) with scoped Wealthbox/Quiltt/Mercury/etc. read access + Word/PDF/Google Docs output via existing pandoc + xelatex toolchain. Phase-0 PoC → Phase-1 first production deliverable → Phase-2 productize sequencing proposed. Highest-strategic-leverage product investment available; sequenced behind doc-cleanup completion + polling-loop v2 Phase 0.5 spike + pw-onchain absorption Slices 4+.

  • CTO/CISO sole approval. Doc + dispatch infrastructure + global Claude Code classifier-rule settings; no compliance content modified; no firm-state behavior change; no production code touched outside the public-repo hygiene + per-repo CLAUDE.md retargets.


2026-05-14 (Phase 3 ALREADY-BROKEN cleanup landed — 4 PRs auto-merged)

  • Four cross-repo cleanup PRs landed in a 3-minute window following the cascade iteration close; cli1 + cli2 dispatched at 9848b3b worked sequentially across distinct worktrees and all four auto-merged on green CI: pw-os-v2 #331 (9e2e570, 20:50:13Z) → pw-api #215 (0dc9663, 20:51:00Z) → pw-infrastructure #187 (b76b251, 20:51:55Z) → pw-portal-v2 #59 (f0390d7, 20:53:00Z). All Phase 3 ALREADY-BROKEN path substitutions deferred from the cascade PRs (#214 / #58 / #330 / #186) now folded in. Total: 41 substitutions across 36 files — pw-api 23 files / 33 lines (drift +3 vs ~20 expected; within ±5 STOP gate; driven by one line-wrapped PW-ARCHITECTURE-DEEP-DIVE ref in migrations/0017 that the \.md-anchored Phase 1 diagnostic missed + two shared/api/* glob refs in strategies modules); pw-os-v2 8 substitutions across 7 files (drift 0); pw-infrastructure 4 substitutions across 4 files (drift 0); pw-portal-v2 6 substitutions across 3 files (drift 0).
  • Auto-merge discipline validated end-to-end on its first multi-repo test post-ritual-hardening. cli2 closed the gap from #330 (autoMergeRequest non-null on both pw-os-v2 #331 + pw-infrastructure #187 within 60s of PR create per setpoint #1); cli1 likewise on pw-api #215 + pw-portal-v2 #59. Ritual hardening addition #3 (auto-merge enable + verify) survives contact with reality on its first real multi-repo run.
  • Phase 4b runtime-concat sweep yielded zero hits across all four repos — second confidence-building negative validation of the sweep pattern added after pw-website #96 surfaced the regex-blind spot at scripts/compliance-lint.mjs:242.
  • Operator-decision item surfaced (both workers, independently): shared/archive/state/PW-ARCHITECTURE-DEEP-DIVE-2026-04-24.md (48382 bytes, md5 6be674dda968de9841ff14877387d2cf) and shared/archive/status/PW-ARCHITECTURE-DEEP-DIVE-2026-04-24.md (48308 bytes, md5 89f6ec13a09fd028ce2d29b618beb976) both exist post-reorg with divergent content. cli1's pw-api #215 retargets references to archive/status/; cli2's pw-os-v2 #331 retargets to archive/state/. Net effect: pw-api and pw-os-v2 now point at different archive locations for the same logical file. Queued for follow-up content-fix dispatch (per Queue B in strategy/dispatch-next-iteration-queue.md) — pick canonical, delete non-canonical duplicate from shared/, realign both consumer repos to one location. Cross-repo PR sequence: 1 PR in shared/ (dedupe) + 1 PR in pw-api (realign if non-status/ is canonical) + 1 PR in pw-os-v2 (realign if non-state/ is canonical).
  • Worker-launch-ritual git add -A pattern flagged for v2 hardening. Both cli1 and cli2 staged via git add -A per the ritual's Phase 4 example; the global Claude Code CLAUDE.md cautions against -A in favor of by-name staging (sensitive-file inclusion / large-binary risk). Not a live problem in either Phase 3 PR (diffs were entirely doc/path-substitution; no untracked sensitive files in the worktrees), but the ritual's example is the canonical worker reference and should be hardened to either by-name staging or an explicit "verified-clean-worktree" pre-check.
  • CTO/CISO sole approval. Path-canonical changes across four repos; no compliance content; no behavior change; routine cleanup chain.

2026-05-14 (post-reorg cross-repo cascade landed + dispatch-pattern hardening)

  • Five-PR cross-repo cascade landed within the cascade iteration. The post-2026-05-14 tiered reorg (pw-shared #87 at d50b59d) propagated through all five active code/doc repos via sequenced cascade dispatches. PR ladder: pw-website #96 (a0013a1) → pw-api #214 (bad26ef) → pw-portal-v2 #58 (f8ca59a) → pw-os-v2 #330 (082df26) → pw-infrastructure #186 (a575625). All Phase 2 NEWLY-BREAKING substitutions applied; ALREADY-BROKEN cleanup + operator-decision items deferred to follow-up work (queued at strategy/dispatch-next-iteration-queue.md). Phase 4b runtime-concatenation sweep added after pw-website #96 surfaced a regex-blind spot at scripts/compliance-lint.mjs:242 (join(SHARED, "privacy.md") slipped past the literal-path regex because the literal had no shared/ prefix); zero hits across the subsequent four cascade repos, validating the sweep as confidence-building negative for an infra-heavy cascade.
  • Operational vehicles validated three times each: (a) strategy/2026-05-15-cross-repo-cascade-prompts.md as the authoritative move table (drafted earlier in the iteration, refined live as the pw-website learning surfaced); (b) dispatch/ inbox-on-disk pattern (shipped same iteration at 9d38dfc) replacing chat-pasted prompts with pending.md + worker-launch-ritual.md for prompt-durability across display issues, lossy clipboards, and chat-rendering quirks; (c) parallel-worker discipline (cli1 sequencing pw-api → pw-portal-v2 in separate worktrees; cli2 alone on pw-os-v2; cli1 follow-up on pw-infrastructure) honoring single-agent-per-repo.
  • Same-iteration discipline hardening — chore(dispatch): ritual hardening from cascade learnings at 9ba0e39. Five hardening additions to dispatch/shared/worker-launch-ritual.md + dispatch/README.md, each traced to a specific cascade incident: fresh-CLI-session-per-task (cli1's reused-session refused to engage with the new pw-infrastructure dispatch), CWD-anchor-every-Bash (cli1's pre-flight surfaced Shell cwd was reset to /mnt/c/Users/nick in WSL between Bash calls), auto-merge-enable-and-verify (cli2's pw-os-v2 #330 returned autoMergeRequest: null after PR create and sat OPEN until operator intervention), completion-report-inline-in-archived-pending.md (all four worker archives were verbatim prompt text only; orchestrator had to reconstruct worker output from PR body + diff), done/-filename-status-suffix (<timestamp>-<status>.md for grep-scannable dispatch history).
  • Polling-loop v2 architecture queued at strategy/dispatch-control-architecture.md (operator-review-gated design proposal, not implementation). Treats dispatch as a control-systems engineering problem: three nested control loops (inner 30s file-state polling; middle 5-min batch-progress monitoring; outer per-iteration pattern detection and setpoint adjustment); quantitative setpoints with measurable error functions per task class; stability mechanisms (rate-limited control actions, hysteresis on intervention thresholds, circuit breakers on autonomous retry loops); classical-control vs AI-driven distinction explicit; prior art from Kubernetes controllers, Netflix Spinnaker, adaptive control with divergence rationale where PW design departs. Six observed setpoints from this iteration's cascade captured for the setpoint table (auto-merge verification, Phase 1 baseline drift, Phase 4b yield, completion-report channel, wall-time bounds, session-reuse contamination). Design eliminates the first / fourth / fifth hardening additions structurally; second + third remain operational discipline regardless of dispatch generation.
  • Follow-up queue captured at strategy/dispatch-next-iteration-queue.md (iteration-boundary handoff artifact, not date-stamped per state-based naming convention): Phase 3 ALREADY-BROKEN mechanical cleanups (4 repos), operator-decision content fixes (pw-os-v2 PW-SECURITY-POSTURE-* glob + cdf.md aspirational ref; pw-infrastructure open-source-inventory + covered-ui-checklist legacy refs), docs/canonical-references.md rewrite PR (25 speculative refs requiring per-row filesystem-state audit, not mechanical sed-replace), weekend hygiene queue (pwos-core/nexus-core flags + pw-website doc-text refs + plaintext-credentials remediation), polling-loop v2 implementation queued behind architecture-doc review approval.
  • CTO/CISO sole approval. Path-canonical changes only across the cascade; no compliance content modified; no Marketing Rule / 17a-4 / Reg S-P surface touched. Cascade PRs auto-merged on green CI per repo conventions; pw-os-v2 #330 required operator manual merge after cli2 missed the auto-merge enable step (gap closed by ritual hardening above).

2026-05-14 (evening — shared/ tiered reorg)

  • refactor(shared): tiered reorganization landed. docs/ consolidation for canonical firm + compliance markdown (5 firm operating docs to docs/firm/; 16 compliance program documents to docs/compliance/); compliance/reg-s-p/signed-pdfs/ split into docs/pdfs/ (4 current pre-sign drafts) + docs/signed/ (DocuSign returns; empty) + docs/versions/<doc-base-name>/ (3 prior-version drafts); AGENTS.md merged into CLAUDE.md as the new "Agent Coordination Protocol" section; llms.txt deleted (zero cross-repo consumers verified across all eight repos in the active estate); shared/status/ + shared/inbox/ + architecture/api/fmp.md archived to archive/; compliance/changelog.md renamed to compliance/COMPLIANCE-CHANGELOG.md; CHANGELOG quarterly split policy documented at archive/changelog/README.md (current quarter at root, older months to per-month archive files at quarter boundaries; no entries split yet since the cross-repo CHANGELOG started 2026-04-26).
  • Intra-shared reference cascade: 27 living markdown files updated via perl two-phase substitution (shared/-prefixed + bare-relative with negative lookbehind to prevent double-substitution) to point at the new docs/firm/ and docs/compliance/ paths. Pre-existing dangling references opportunistically fixed during the same cascade (SHARED/governance/disclosures.jsonshared/disclosures.json; SHARED/governance/ria.mdshared/docs/firm/ria.md; shared/vault/tax-ref.mdshared/docs/firm/tax-ref.md; shared/PW-SECURITY-POSTURE-INTERNAL-v1.0.mdshared/docs/compliance/security-posture-partner.md). Dated audit-trail records (cco-approvals/, decisions/, vendor-correspondence/, protocols/, archive/, this CHANGELOG, COMPLIANCE-CHANGELOG.md) deliberately left unchanged per audit-trail discipline (state-as-of-capture-date).
  • Status-doc compile workflow retired (skills/session-end-hygiene/SKILL.md Step 3 marked RETIRED 2026-05-14): the PW-STATUS-AND-CAPABILITIES daily compile is superseded by live state anchors — strategy/CURRENT-STATE.md (live firm state), shared/CHANGELOG.md (cross-repo PR ledger, append-on-merge), per-repo CHANGELOGs (repo-level activity), docs/compliance/governance-dashboard.md + ciso-dashboard.md (pending sign-offs), and pwos.app/workstreams (partner-tier action items). Historical PW-STATUS-AND-CAPABILITIES snapshots remain in archive/status/ as frozen state-of-the-firm captures; new compiles are no longer produced.
  • ~36 newly-breaking cross-repo references queued for follow-up cascade PRs in pw-api, pw-os-v2, pw-portal-v2, pw-infrastructure, and pw-website (planned 2026-05-15). These reference the pre-reorg paths (shared/AGENTS.md ×9, shared/privacy.md ×4, shared/compliance/wisp-compliance-hub-plan-2026-04-29.md ×5, shared/compliance/newsletter-tags-taxonomy.md ×3, plus shared/inbox/* ×7 and others). Per the reorg out-of-scope clause, these stay out of this PR; consuming repos cascade separately.
  • Targeted fixes alongside the reorg:
    • compliance/audits/blockskunk-phase-0-input-package.md:140 — dangling reference to non-existent shared/compliance/posture/ciso-appointment-letter-2025-12-31.md corrected to the actual canonical file shared/governance/CISO Appointment - CTO/CISO.pdf.
    • docs/firm/terminology-map.md — three forward-references to unbuilt artifacts (SHARED/strategy/copy.json, vision.md, roadmap.md) annotated inline as "(planned; not yet authored)" to preserve intent visibility while clearly marking not-yet-implemented.
  • Positive finding worth noting: the rename targets privacy-policy.md and terms-of-service.md were ALREADY cited as canonical in reg-s-p-compliance-record-2026.md lines 186-187 prior to this reorg. The compliance record had documented these paths as canonical; this reorg realizes the documented intent.
  • CTO/CISO sole approval. No CCO sign-off required — path moves only; no content changes to compliance documents. CCO reviews the rendered compliance docs via DocuSign as the natural review surface. Reorg-only changes recorded in compliance/COMPLIANCE-CHANGELOG.md by Rule 204-2 obligation: every path-canonical change to a compliance document is logged there even when the document's content is unchanged.
  • PR open for manual merge (auto-merge disabled per CTO/CISO directive on structural-change PRs); Gate C is CTO/CISO review.

2026-05-14 (evening — Chainalysis + Zapper removed from compliance docs)

  • docs(shared): pre-DocuSign-send vendor reference correction across the compliance document set. PW does not use Chainalysis and does not use Zapper; three CCO-signed PDFs (KYC/AML Policy, Reg S-P Compliance Record, Customer Breach Notification Template) and their cross-doc dependencies (Subprocessors Inventory, partner Security Posture, BlockSkunk audit input, AML/OFAC gap map) carried stale vendor references that CTO/CISO caught pre-send. Single PR substitutes the actual stack: Veriff for TradFi KYC + onboarding sanctions, Scorechain (Free Sanctions API for OFAC/SDN + Risk Assessment API via QuickNode for chain-scoped KYT/KYW on BTC/ETH/SOL/Base/XRP/AVAX) for onchain compliance, DeBank primary + Octav backup for onchain position aggregation; manual CCO review with OpenSanctions + public block-explorer tools is the documented fallback for activity on chains outside Scorechain's supported set.
  • Version bumps: KYC/AML Policy v1.0 → v1.0.1; Reg S-P Compliance Record v2026.0.1 → v2026.0.2; Subprocessors Inventory v1.1 → v1.2 (in-content; filename retained as pw-subprocessors-v1_1.md to avoid cascading cross-reference breaks); Customer Breach Notification Template unchanged at v0.3 (no Chainalysis references found; per task conditional, leave unchanged). Partner Security Posture v1.1 + gap map + BlockSkunk audit input edited in place.
  • PDF regeneration: kyc-aml-policy-v1_0_1.pdf + reg-s-p-compliance-record-2026-v2026_0_2.pdf rendered via the existing pandoc + xelatex pipeline (no --signature flag — both docs have native sign-off blocks); landed in compliance/reg-s-p/signed-pdfs/. Previous v1.0 / v2026.0.1 PDFs preserved as historical artifacts.
  • Q1 (Scorechain capability surface) and Q2 (onchain vendor verification) resolved by CTO/CISO mid-PR: Q1 → use exact product names from architecture/api/scorechain.md (Free Sanctions API + Risk Assessment API via QuickNode); Q2 → DeBank confirmed primary via pw-api/src/lib/debank.ts, Octav added as backup per Secret Manager credential bindings, Zerion removed (no integration, no credential), Zapper removed (no integration, no credential). Three "Scorechain entity-type" rewrites in KYC/AML Policy §8 carry inline TODO comments flagging the specific capability claims for CTO/CISO final-pass review before sign-off.
  • CTO/CISO + CCO sole approval (observability + policy-text correction; no posture change requiring CCO pre-sign on AML obligations or Reg S-P controls). Compliance posture-corrective only — CCO reviews the corrected PDFs via DocuSign as the natural review surface.

2026-05-14 (afternoon — three terraform applies cleared)

  • infra(pw-infrastructure): three terraform workspaces applied in order. cloud-run-baselinemonitoringsecrets. End-state: all four Cloud Run services pinned at min=1/max=2 except pw-api-webhooks (intentional 1/5 for Quiltt burst capacity); pw-portal carries ANTHROPIC_WORKSPACE_ID; egress-canary metrics + alerts ENABLED in prod; no remaining state drift.
  • cloud-run-baseline: first apply attempt at 15:30Z failed on pw_portal while the broken revision was still unresolved (pre-#185 merge). Second apply ran cleanly post-#185 (15:44Z window). Plan output read oddly because Terraform 6.x normalizes scaling blocks differently than the prior provider — min_instance_count = 0 → null lines surfaced on all four services, but those were cosmetic provider-version noise on the already-pinned pw_os/pw_api_webhooks; the real change was the pw_portal env add plus pw_api/pw_portal scaling codification per #185.
  • monitoring: clean apply; 2 dashboards re-serialized (cosmetic JSON normalization — etag field removed and xPos/yPos field reordering). gcloud alpha monitoring policies list confirmed both pii_egress_canary_blocked + pii_egress_detector_mismatch alerts are ENABLED in prod. Worth noting for the record: #183's metrics + alerts were already in state when this workspace applied — a prior CI auto-apply earlier in the day had absorbed them before this orchestrator session noticed. Re-apply was a no-op for those resources.
  • secrets: No changes. Your infrastructure matches the configuration. The Finnhub key removal from #182 had likewise been absorbed at the prior auto-apply. Workspace in sync.
  • Local toolchain side-notes (not prod, captured for follow-up): gcloud alpha monitoring required a component install mid-session (didn't have alpha installed locally). operator's gcloud bundled Python threw ruamel.yaml and oauth2client ImportErrors during the live-service-state describe loop — cosmetic broken-dependency on the local Cloud SDK install, not a prod issue. Recommended fix later: reinstall gcloud or update components.
  • CTO/CISO sole approval. Apply-only entry; the underlying PRs (#182 / #183 / #185) carried their own approvals.

2026-05-14 (afternoon — pw-portal deploy incident + closure — pw-infrastructure #185)

  • fix(pw-infrastructure): closure of the pw-portal startup-assertion incident; same PR codifies a pre-existing scaling drift. pw-portal-v2 #57 (merged 14:14Z) added apps/api/src/lib/startup-assertions.ts, a boot-time guard that calls process.exit(1) in production when ANTHROPIC_WORKSPACE_ID is unset. The pw-portal Cloud Run service had no such env var; revision pw-portal-00067-w5p auto-deployed at 14:16Z, the container booted, printed pw-portal-v2 API starting on port 8080, then logged refusing to serve traffic and exited 1. Cloud Run held 100% traffic on prior healthy revision pw-portal-00065-gtf (which predates the assertion code and so was unaffected). The public endpoint stayed 200 throughout; no client impact at any point.
  • Diagnosis (~10 min total): (1) gcloud run services describe pw-portal showed latestReadyRevision=00065, not 00067. (2) gcloud run revisions logs pw-portal-00067-w5p showed the boot banner followed by refusing to serve traffic. (3) Read of apps/api/src/lib/startup-assertions.ts named the missing env var. Three steps, no false starts.
  • Fix (pw-infrastructure #185, merged 15:43Z):
    • Adds ANTHROPIC_WORKSPACE_ID env block on the pw_portal Cloud Run service in terraform/cloud-run-baseline/cloud-run-services.tf — literal fe30b317-0a9c-47df-a972-b9611e6e0002, mirroring the existing pw_os block (one ZDR workspace, both services).
    • Same-PR scope addition: pins scaling explicitly on pw_api (was 0/10) and pw_portal (was 0/10) to min_instance_count = 1 / max_instance_count = 2, codifying the previously-live state from pw-infrastructure #178. Closes the min_instance_count = 0 → null apply-drift that the workspace's plan output had been carrying on all four services. pw_os already correctly pinned (1/2); pw_api_webhooks intentionally left at 1/5 as the legitimate-difference exception (Quiltt 20s response-window burst capacity per existing inline rationale comment) — called out in PR body.
  • Closure: terraform apply post-merge created pw-portal-00068-kdg with the env var set; revision went Ready, took 100% traffic. The boot assertion implicitly passed (the assertion exits on failure, so Ready + 200 response is the behavioral proof). Incident closed at 15:44Z.
  • Why this is a finding, not just a fix: auto-merge has no post-deploy health gate. A PR that adds a process.exit(1) boot requirement should not be able to silently ship a broken revision and only get caught because we happened to touch terraform next. Captured as a 2026-05-14 process finding in strategy/2026-05-14-process-findings.md along with three other lessons from today's session.
  • CTO/CISO sole approval. Same-day incident fix; doc-only PR body discussion of the scaling pin (no service got unpinned).

2026-05-14 (afternoon — egress canary metrics + alerts + runbooks live in prod — pw-infrastructure #183 + #184)

  • infra(pw-infrastructure): Cloud Logging log-based metrics + alert policies + runbooks for the PII egress canary now end-to-end in prod. Closes the P1-ish follow-up flagged in this morning's pw-os-v2 #329 + pw-portal-v2 #57 entry. Application-side canary already aborted any Anthropic-SDK egress carrying residual PII; this PR pair lights up the alerting + operator-response surface.
  • pw-infrastructure #183 (merged 15:29Z): two log-based metrics on jsonPayload.action="pii_egress_canary.blocked" and on jsonPayload.action="pii_egress.request" with the field-to-field filter jsonPayload.turns_scanned < jsonPayload.turns_in_request (honest-metrics drift signal — surfaces when a user-role turn reached the SDK egress unscanned). Two alert policies wired to the existing gchat_alerts notification channel, 300s evaluation window, 7200s auto_close. Field-to-field comparison lives in the metric filter, not the alert threshold (Cloud Monitoring alert conditions can't express field-to-field; the filter approach surfaces the violation at metric-ingest time and the alert is a simple "metric ticked > 0" gate). terraform/monitoring/dashboards.tf + alert-policies.tf, 229 insertions.
  • pw-infrastructure #184 (merged 15:35Z): two operator-facing incident runbooks at pw-infrastructure/docs/runbooks/pii-egress-canary-blocked.md + .../pii-egress-detector-mismatch.md. Reg S-P §248.30(b) framing — every canary block is a missed scrub by the upstream Contract #3 PII guard, so filing a remediation ticket is non-optional regardless of root cause. Path correction worth flagging: the original task brief said shared/runbooks/, but #183's alert documentation.content blocks link to https://github.com/Protocol-Wealth/pw-infrastructure/blob/main/docs/runbooks/...; the CLI agent correctly verified against the live URL prefix and placed the runbooks where the alert triage links resolve. pw-infrastructure/docs/runbooks/README.md index updated with both entries.
  • Confirmed ENABLED in prod via gcloud alpha monitoring policies list --filter='displayName:pii_egress' after the monitoring workspace apply: pii_egress_canary_blocked ENABLED, pii_egress_detector_mismatch ENABLED, both targeting gchat_alerts. Defense-in-depth posture is end-to-end: application canary aborts on residual PII → structured log → metric → alert → operator runbook → CCO loop-in.
  • CTO/CISO sole approval. Infra-only; no business-logic change; security-posture strengthening.

2026-05-14 (independent PII egress canary — pw-os-v2 #329 + pw-portal-v2 #57)

  • feat(pw-os-v2, pw-portal-v2): independent PII egress canary as defense-in-depth Reg S-P §248.30(b) safeguard. Adds a standalone egress-time check on every Anthropic-SDK outbound call: if the raw request body matches an independently-implemented PII pattern set, throw PIIEgressCanaryError and surface as pii_blocked failure class without leaving the process. This sits behind the existing PII redaction (Contract #3 pii.ts) and exists specifically to catch a future regression where the upstream scrubber silently drifts — if pii.ts ever misses a class, the canary still does. Pattern set is deliberately re-implemented from scratch (no pii.ts import) so the two layers cannot share a bug. A shared module would defeat the design and is acknowledged as future work; the byte-identical field-name set across the two canary copies is pinned by field-name-pin unit tests in each repo (cross-repo contract).
  • pw-os-v2 (feat/pii-egress-canary, #329): new apps/api/src/lib/pii-egress-canary.ts standalone module. Wired at 5 SDK egress sites: claude-client.ts (chat stream), workstreams/summarizer.ts (sync + streaming), compliance/analyze.ts, social-drafts.ts. chat.ts passes piiMetrics with honest turnsScanned (incremented at every actual scan() call site). observability.ts classifyFailure recognizes PIIEgressCanaryErrorpii_blocked failure class. 24 new tests + 3 modified; 1116 total passing.
  • pw-portal-v2 (feat/pii-egress-canary, #57): egress canary mirror (modulo REPO_TAG). Wired at the raw-fetch egress in anthropic-client.ts:streamMessage. chat.ts passes piiMetrics; multi-turn test agrees with the existing AX.2 contract test. PIIEgressCanaryError gets a dedicated SSE branch (kind='pii_egress_blocked'). 18 new tests; 105 total passing.
  • pw-api correctly skipped. pw-api makes ZERO Anthropic-SDK calls (no @anthropic-ai/sdk import anywhere); it is the vendor-data bridge only (FRED / MBOUM / EDGAR / Marketstack / Tradier / Brave / DeFiLlama / Wealthbox / etc.). pw-api/src/config/claude.ts is model-ID constants for downstream consumers, not a client. Anthropic-calling repos are pw-os-v2 + pw-portal-v2 only.
  • Cross-repo contract: egress canary field-name set is byte-identical across pw-os-v2 and pw-portal-v2, pinned by field-name-pin unit tests in each repo. Blocked-row schema identical save the REPO_TAG. Two in-sync copies, deliberately not a shared module — the independence is the point.
  • Handoff (follow-on, NOT in these PRs): pw-infrastructure Cloud Logging log-based metric + alert on pii_egress_canary.blocked events and on turns_scanned < turns_in_request (an honest-metrics drift signal). Tracked as a P1 roadmap follow-up.
  • Closes: AP0.3 / Section A defense-in-depth follow-on from the overnight audit (PII guard had no egress-time backstop; now does).
  • CTO/CISO sole approval. Security-posture strengthening; no business-logic change to Claude responses; no client data touched in the change itself.

2026-05-14 (terraform Finnhub dead-vendor removal — pw-infrastructure #182)

  • chore(pw-infrastructure): remove Finnhub terraform residue. Finnhub was decommissioned from the pw-api scan-bundle stack 2026-05-13 (pw-api #206 / FMP+Finnhub restructure). The application-side removal landed cleanly; the terraform side carried three dead Finnhub artifacts forward: FINNHUB_API_KEY env-var block in cloud-run-baseline/, finnhub-api-key entry in the app-deployers/ secret map, and 3 Finnhub rows in the secrets/ README. This PR removes all three (3 files changed, 8 deletions). fmt + validate clean across secrets/, cloud-run-baseline/, and app-deployers/.
  • NOT a continuation of #181. pw-infrastructure #181 already finished the FMP terraform removal cleanly (env-var block, secret-map entry, README rows, and gcloud example all removed in #181); the FMP audit-finding AP0.7 closed in #181 + pw-api chore/remove-fmp-integration. This PR is the Finnhub dead-vendor cleanup, parallel to but separate from the FMP work. No remaining FMP residue exists post-#181.
  • Apply order (manual, post-merge; WIF + state lock): cloud-run-baseline + app-deployers first, then secrets last. CTO/CISO applies; serialize per feedback_serialize_parallel_infra_merges.
  • Out of scope / flagged for follow-up: dead fmp:* Redis-namespace example in terraform/monitoring/alert-policies.tf:260 (cosmetic; carries forward from #181); consumer-attribution drift in terraform/secrets/README.md (pw-nexus vs pw-api on ~6 vendor rows; pw-nexus retirement cleanup). Both tracked on the roadmap, not this PR.
  • CTO/CISO sole approval. Dead-vendor cleanup; no live service references; terraform-only.

2026-05-14 (scan-bundle profile chain restored — MBOUM primary, EDGAR-SIC last resort)

  • feat(pw-api, pw-portal-v2): scan-bundle sector / industry / description restored. The FMP teardown (chore/remove-fmp-integration, merged earlier today) left /scan-bundle Step 2 null on those fields with a roadmap-follow-up comment. This PR pair fills them in: pw-api wires MBOUM module=profile as primary (Yahoo-mirroring shape, ~99% US-listed coverage, included in the Business tier we already pay for; never wired previously due to a stale "MBOUM has no profile shape" comment that turned out to be wrong) → EDGAR submissions sicDescription + a lazy 2-digit-SIC-prefix sicToSector() table as last resort (description stays null on this branch — EDGAR carries no longBusinessSummary equivalent). ETFs the classifier short-circuits produce profile_source=null (no useful sector mapping for funds).
  • pw-api (feat/scan-bundle-profile-mboum-primary): new src/lib/mboum.ts fetchMboumProfile (Zod-schema'd against the inferred response shape — schema drift throws MboumAPIError(upstream) so the first real call surfaces a mismatch loud; the endpoint was NOT exercised live during the FMP-removal inventory). New src/lib/sic-to-sector.ts with ~30 seeded prefixes (lazy growth). New src/routes/scan-data.ts Step 2 wires the chain; new ScanBundleResponse.profile_source: 'mboum' | 'edgar_sic' | null signal + scan_bundle.profile_resolved per-ticker structured log. New narrow endpoint GET /v1/internal/market/profile/:symbol (same chain) for consumers that don't need the full scan-bundle. 21 new unit tests across 3 files (mboum-profile.spec.ts / sic-to-sector.spec.ts / scan-data-profile.spec.ts); 818 total pass; lint clean. AlphaVantage deliberately NOT wired as a fallback (separate decision, out of scope).
  • pw-portal-v2 (fix/markets-profile-repoint): apps/api/src/routes/markets.ts proxies the restored chain via the narrow /v1/internal/market/profile/:symbol endpoint, replacing the removed /v1/internal/market/fmp/profile. Markets tile field mapping adapted to the new response shape; CLAUDE.md doc drift ("quote + FMP company profile + MBOUM news") fixed. Tile degrades gracefully for symbols where both MBOUM and EDGAR-SIC miss (empty profile section, no crash — existing missing-profile path).
  • Why now: the FMP teardown opened a sector-signal gap in pw-os-v2's 7-layer durability model. layer_classifier.ts gracefully tolerated empty-string sectors (|| '' guards), but the EMF scoring path ran without sector signal — fine as a temporary degradation, not as a permanent state. MBOUM module=profile was always the right primary; it just wasn't wired. Cost: zero — already paying for the Business tier.
  • shared/architecture/api/mboum.md — adds the inferred response shape for module=profile (body.assetProfile.{sector, industry, longBusinessSummary, country, website}) flagged as INFERRED-not-exercised-live until the post-deploy first-call validation confirms the shape.
  • Deploy gate (manual, post-deploy): first-real-call validation against a US large-cap; confirm profile_source='mboum' with non-null sector/industry. Schema drift will throw clean and fall to EDGAR-SIC; check Cloud Logging for mboum.profile.schema_mismatch.
  • Closes: the sector-signal gap in pw-os-v2's 7-layer durability model. The cross-repo follow-up flagged by chore/remove-fmp-integration (pw-portal-v2 markets tile dangling proxy).
  • CTO/CISO sole approval. Internal data-pipeline restoration; no client data; paid + free ToS-clean sources.

2026-05-14 (FMP integration removed entirely — closes audit AP0.7)

  • chore(pw-api, pw-os-v2): remove FMP (Financial Modeling Prep) integration entirely. FMP was flag-off in prod since 2026-05-13 (PR #204) and fully superseded by the MBOUM Business primary + Marketstack Basic price secondary + SEC EDGAR XBRL fundamentals canonical + Twelve Data quaternary chain. The retained API key and dead helper code were the only thing keeping the audit finding AP0.7 (FMP free-tier ToS §2.2.1 commercial-use exposure) live. This PR rips the integration out across both repos.
  • pw-api (chore/remove-fmp-integration): deleted src/lib/fmp.ts (765 LOC FMP client lib); deleted docs/integrations/fmp.md; deleted 3 FMP-centric test files (test/unit/cache-policy.spec.ts, test/unit/market-data-expansion.spec.ts, test/unit/scan-data.spec.ts, test/unit/scan-data-fmp-disabled.spec.ts). Removed the 8 /v1/internal/market/fmp/* routes from src/routes/internal.ts (profile / income-statement / balance-sheet / cash-flow / key-metrics / etf-holdings / etf-sectors / etf-countries / insider-trading). Removed FMP_API_KEY + FMP_FALLBACK_ENABLED from src/config/env.ts. Removed FMP from src/routes/scan-data.ts — imports + fmpStatementsToScanBundle, fmpErrorCode, runFmpFallbackCall helpers + Step 2 profile fetch + Step 6 FMP legacy fallback + 'fmp' from data_source / providers_attempted / error_codes unions + FMP branches in winner logic + FMP in error-code coalesce + unused numOrNull helper. Removed FMP TTL row from src/lib/cache/cache-policy.ts and 'fmp:' namespace from cache-bust allowlist. Surgically pruned 11 incidentally-FMP-referencing test files (env vars, mock fns, response shape types). Updated AGENTS.md + README.md + CLAUDE.md provider-chain docs.
  • pw-os-v2 (chore/remove-fmp-integration): deleted 4 FMP-only chat-tool bridges (get-company-financials.ts, get-company-metrics.ts, get-etf-breakdown.ts, get-insider-trading.ts); excised FMP hop from multi-vendor get-quote.ts (cascade now MBOUM→Tradier; tool remains functional); excised get_company_profile from market-quotes.ts (kept MBOUM/Tradier/DeFiLlama tools). Removed 4 deleted-bridge imports from tools/index.ts. Removed 'fmp' from ScanError.provider union + Fundamentals.dataSource + scoringProvider; widened dataSource/scoringProvider to include 'edgar' / 'twelvedata' / 'mboum_thin_etf' (mirror of pw-api widening). Removed providerFromBundleCode() fmp_ branch from scan_runner.ts. Removed fmp_fallback_invocations/fmp_fallback_successes/fmp_fallback_failures/fmp_thin apiCallCounts entries. Restructured ScanError attribution (mboumAttempted && otherAttempted → 'multi'). Removed FMP vendor row from seed-vendors.json. Updated chat-tool count discussion in CLAUDE.md (53 → 48). Test fixtures retargeted from FMP to EDGAR / MBOUM where possible.
  • Behavior changes (deliberate; called out for examiners):
    • scan-bundle sector / industry / description / market_cap now resolve null. FMP profile fetch was unconditional (Step 2) and is removed with no in-PR replacement. EDGAR-SIC sector mapping is tracked as a P1 roadmap follow-up. pw-os-v2 apps/api/src/lib/scanning/layer_classifier.ts degrades gracefully (empty-string guards at :53-54); the EMF durability model runs without sector signal until the EDGAR-SIC follow-up lands. (Reverted by the profile-restoration PR above, 2026-05-14.)
    • get-quote.ts cascade reduced from MBOUM→FMP→Tradier to MBOUM→Tradier. Tool stays registered. Both surviving paths verified passing via the existing unit test (apps/api/src/lib/tools/bridges/get-quote.test.ts).
  • Cross-repo follow-up (NOT in this PR — flagged for CTO/CISO): pw-portal-v2/apps/api/src/routes/markets.ts:158 proxies the removed /v1/internal/market/fmp/profile endpoint; MarketsTile.tsx will render empty profile sections for all symbols post-merge until pw-portal-v2 drops the call or repoints to the EDGAR-SIC follow-up. Per task scope ("pw-portal-v2 not in scope — report, don't fix"). UX impact: degraded display, not crash (existing code anticipates "many ETFs / OTC names don't have an FMP profile"). (Resolved by the profile-restoration PR above, 2026-05-14.)
  • Deploy-gate step (manual): CTO/CISO removes FMP_API_KEY from Secret Manager (pwllc-fmp-api-key) post-merge. Not a code step; not in this PR.
  • Closes: pw-api #180 (FMP /stable/etf-* 404 — moot, integration removed). pw-api #186 / #187 (scan-data error_code disambiguation — superseded; per-provider error_codes: {mboum, marketstack, edgar, twelvedata} shipped with synthetic both_providers_thin retained). Audit finding AP0.7 (FMP free-tier commercial-use exposure).
  • CTO/CISO sole approval. Dead-code removal; no client data; no behavior change to live provider chain. 797 pw-api unit tests pass; 1084 pw-os-v2 api unit tests pass; lint clean both repos.

2026-05-14 (public-repo honesty posture — nexus-core + pwos-core disclaimers; PWOS.md §8.1 inspected)

  • docs(nexus-core, pwos-core): public-repo honesty disclaimer. Both public READMEs gain a ## Status block before any capability description: "This is a reference framework and a starting point, not a production-ready product. It is a work in progress under active, iterative development. Adopters are responsible for adding their own PII controls, access control, input validation, authentication, and data-handling boundaries appropriate to their own regulatory and security context before any real or sensitive data touches it. Adopters are also responsible for their own AI-provider data-handling posture; the framework makes no data-retention guarantees. Provided as-is under Apache-2.0." No ZDR / US-inference language was added to the public repos: ZDR is an account-level configuration of PW's own Anthropic workspace, not a property of the framework code; stating or implying it is built in would be the same false-claim error this PR exists to fix.
  • docs(nexus-core): claim reconciliation per AP1.12 (overnight audit finding). README.md architecture diagram lines "Multi-tier access control" + "Transport-layer PII filtering" under MCP Tool Registry replaced with "Pluggable ResponseFilter hooks (adopter-supplied auth / PII / audit)" — honest match to the actual code at src/nexus_core/mcp/server/app.py which exposes a filters: list[ResponseFilter] hook surface and ships no auth / tier / PII filtering of its own. docs/ARCHITECTURE.md MCP Tool Pattern code example rewritten (prior example called a check_tier(...) function that does not exist in the codebase); "Tiered Access" table (PUBLIC / USER / CLIENT / ADVISOR) replaced with an "Access Control and Tiering (Adopter-Supplied)" section that states plainly the framework does not enforce access tiers and the scaffold treats all callers as trusted.
  • docs(shared): architecture/PWOS.md §8.1 inspected; no change. The "Anthropic API under a Zero Data Retention workspace, US-only inference" posture is already stated plainly in §8.1: workspace UUID fe30b317-0a9c-47df-a972-b9611e6e0002, contractual non-retention, training exclusion, and "Geographic restriction: inference restricted to US-based infrastructure" all explicit. No edit warranted; no §18.1 revision-history bump.
  • CTO/CISO sole approval. Doc-only across three repos; public-repo content is regulatory-adjacent (Marketing Rule §206(4)-1 false-claim avoidance) and the change strictly tightens, not loosens. No code, no migration, no posture change.

2026-05-14 (overnight state-doc reconciliation — CURRENT-STATE rewrite + ROADMAP.md seed)

  • docs(shared): rewrite strategy/CURRENT-STATE.md against the live merged-PR ledger for 2026-05-11 through 2026-05-14; seed ROADMAP.md at repo root. Prior CURRENT-STATE was anchored 2026-05-14 EOD but still carried the superseded PR 8 5-bug ledger and the obsolete vendor stack section (MBOUM primary + FMP fallback). Reconciled: (a) Bug #2 + Bug #5 closed pre-cron on 2026-05-11; Bug #3 (FMP fallback chain) is moot — FMP flag-off in prod 2026-05-13 and Finnhub removed entirely; Bug #4 hypothesis-rejected; Bug #1 (worker singleton) effectively retired by PR 8.5 scheduler-tick handler (pw-os-v2 #314, 2026-05-12) — the worker singleton no longer exists. 2026-05-12 09:00 UTC cron outcome left [unverified] (gcloud non-interactive in this session; deferred to interactive verification). (b) Vendor stack rewritten: MBOUM primary, Marketstack secondary, SEC EDGAR XBRL canonical fundamentals fallback, Twelve Data flag-off in prod, FMP flag-off in prod, Finnhub removed. (c) 2026-05-13 + 2026-05-14 PR waves enumerated against gh pr list --state merged per repo. (d) Compliance posture section reflects the three CCO-signing-queue PDFs (KYC/AML v1.0 → Customer Breach Notification Template v0.3 → Reg S-P Compliance Record 2026.0.1) and the BlockSkunk Phase 0/1 SOW signed.
  • New: shared/ROADMAP.md — firm-level planned-improvements register. P0 / P1 / P2 / P3 sections. Each item: what / repo / effort (S/M/L) / gating (CTO-CISO-autonomous / CCO / CIO / external). Seeded from: open issues per repo; 2026-05-13 investment-meeting action items; BlockSkunk Phase 0 dependency chain; Reg S-P June 3 deadline items; deferred work (caching layer Phase 1; ZONAL apply window; C5 cleanup PRs; Langfuse cache TTL observation). Reserved "## Phase 2 — Audit Findings" section at the bottom for an out-of-band overnight read-only audit append.
  • CTO/CISO sole approval. Doc-only; no posture change; no business logic.

2026-05-13 (fundamentals-chain hardening — Finnhub removed; FMP + Twelve Data flag-off; ScanError diagnostic fields)

  • chore(pw-api #203 / #204): structured logs on every FMP fallback HTTP call + FMP_FALLBACK_ENABLED=false set on Cloud Run. Closes the FMP free-tier 429-exhaustion class-of-bug surfaced 2026-05-11 (50 alleged "MBOUM misses" actually masked FMP's 250 req/day free-tier cap). Observability first; flag-off second. Code path retained behind the flag in case of a future paid-tier upgrade decision.
  • feat(pw-api #205): Twelve Data wired as MBOUM secondary fallback (gated). Initial drop-in; validated 2026-05-13 that Twelve Data free Basic tier does NOT include fundamentals endpoints (403 across /income_statement / /balance_sheet / /cash_flow). TWELVEDATA_FALLBACK_ENABLED=false set on Cloud Run; code retained.
  • chore(pw-api #206): Finnhub removed entirely from the scan-bundle stack; Marketstack added as commercial-licensed secondary. Net effect: Finnhub vendor decommissioned (removed from shared/architecture/api/ index; no consumers remain in pw-api). Marketstack inserted as Step 3 (price-only) ahead of the EDGAR XBRL fundamentals step that landed in #208.
  • fix(pw-os-v2 #319): ScanError envelope populates diagnostic fields (http_status, error_code, provider) on every scan-runner failure path + corrupt-payload guard. Companion to pw-api's per-provider error_codes field shipped in pw-api #186/#188. Renames pw-os-v2's enum 'both''multi' in subsequent #321 (already CHANGELOG'd) to accommodate ≥5 providers.
  • Net 2026-05-13 stack state: ~$44/mo paid vendor spend (MBOUM primary only); EDGAR + Marketstack are zero-cost; FMP + Twelve Data flag-off; Finnhub removed. Full chain detail: see entry "2026-05-13 (pw-api EDGAR XBRL fundamentals fallback)" below.

2026-05-14 (PWOS.md v1.2 + canonical-path move + orientation-doc stale-path sweep)

  • docs(shared): PWOS.md v1.2 + canonical-path move + orientation-doc stale-path sweep. PWOS.md moved to its declared canonical path shared/architecture/PWOS.md (was at root pending the move per v1.1 §18 footer). §9.1 retitled "The Claude orchestration pattern (two-to-six streams)" with a rewritten opener reflecting current 2-to-6 stream practice; Layer 1 / Layer 2 paragraphs retained verbatim as the foundational shape (the "floor" the multi-stream model composes from). §9.7 adds an OS-licensing linkage bullet pointing to §15.1 + strategy/multi-agent-coordination.md. §18.1 v1.2 revision entry. Header version bumped 1.1 → 1.2; effective date 2026-05-14; canonical-path footer cleaned (no longer "(move pending)").
  • Bidirectional cross-reference added to strategy/multi-agent-coordination.md at the bottom (above the maintainer footer) pointing back to PWOS.md §9.1 / §9.4 / §9.7. Closes the navigation loop opened in v1.1.
  • Stale-path sweep of three root orientation docs (AGENTS.md, PW-CHAT-CAPABILITIES-AND-ROADMAP-2026-05-01.md, PW-ARCHITECTURE-DEEP-DIVE-2026-04-24.md). Per the brief, grepped each file first and only updated genuinely stale refs. Findings: PR #70's earlier sweep had already closed the bulk; one residual stale ref in PW-CHAT-CAPABILITIES line 416 (shared/PW-STATUS-2026-05-01-EOD.md now at shared/archive/state/PW-STATUS-2026-05-01-EOD.md). PW-ARCHITECTURE-DEEP-DIVE retains its shared/types/, shared/lib/, shared/audit/ references because those are forward-looking design-intent paths (Contract #1/#3 spec target locations, not stale post-reorg paths); leaving them aspirational matches the document's tone. strategy/cdf.md likewise retained as an aspirational draft path ("Phase 0 backtest first" gates the file's existence).
  • Scope expansion (documented): the PWOS.md move broke two references in compliance/reg-s-p/reg-s-p-compliance-record-2026.md (§3 narrative + §4 source-of-truth table). Fixed both as a direct consequence of completing the move — half-finishing the move would leave a compliance doc dangling. Two surgical line edits, no content drift. The brief's step-6 scope guard ("touch only the three named orientation docs") was for the sweep, not for the move's own consequence fixes.
  • CTO/CISO sole approval. Doc-only PR; no posture change; same approval class as PR #75.

2026-05-14 (firm-state EOD sync — source artifacts + nav doc updates)

  • docs(shared): land the source artifacts referenced from PWOS.md v1.1 + today's PRs. PWOS.md (PR #68) cross-referenced four PDFs and one capture document that were intentionally left untracked at the time so a separate workstream could land them. That separate workstream is this PR.
  • Governance PDFs (3, ~1.0 MB total):
    • governance/CISO Appointment - CTO/CISO.pdf — Unanimous Written Consent of Managers, dated 2025-12-31 (DocuSign envelope 39704619-3610-4A29-8456-E227D97AFFAE). Appoints CTO/CISO as CISO with explicit authority over policy, incident response, asset protection, and legal coordination. Signed by CCO and CIO.
    • governance/AML Appointment.pdf — Manager Resolution dated 2026-01-13 (DocuSign envelope 9854DBBF-EB8C-47F1-8071-9D4063C65E47). (a) authorizes Wyoming → Delaware domicile transfer; (b) appoints CCO as AML Compliance Person; (c) renews CCO appointment; (d) confirms CISO designation; (e) appoints CTO; (f) appoints CIO. Signed by all three managers.
    • governance/KYC-AML-Policy.pdf — Enclave Digital Wealth predecessor KYC/AML policy, effective 2025-03-01, signed by CCO as CCO 2025-04-16. Heritage record; superseded by compliance/kyc-aml-policy-v1_0.md (PW-headed reissue from 2026-05-13, PR #64).
  • Reference PDF: reference/Protocol_Wealth_SOW_051126.pdf — signed BlockSkunk Inc. Phase 0 / Phase 1 Statement of Work, $5,000 total, 50/50 payment terms, ISO 27001 + SOC 2 alignment. Source artifact behind PWOS.md §13 and compliance/audits/blockskunk-phase-0-input-package.md.
  • Strategy capture: strategy/pwos-md-delta-2026-05-13-merged.md — the 9-section delta (369 lines) that became PWOS.md v1.1. Committed as a point-in-time capture document so future readers can diff intent vs. final wording; the canonical state lives in PWOS.md.
  • Vendor correspondence: compliance/vendor-correspondence/anthropic-ciso-webinar-2026-05-12/screenshots/ — two screenshots (~644 KB) from the Anthropic "Secure the Advantage: A CISO's Guide to Agentic AI" webinar attended 2026-05-12. Captured for the firm's vendor-engagement record.
  • Navigation doc updates (in the same commit so README + CLAUDE.md stay in sync per the "when a path changes, update both" rule):
    • README.md — directory tree now lists scripts/compliance/ (markdown → PDF pipeline added in PR #63) as a top-level area; governance/ description expanded ("signed Manager resolutions, officer appointment letters, predecessor policies; PDFs are 7-year retained artifacts of firm legal record"); reference/ description expanded to include vendor SOWs.
    • CLAUDE.md — "Where artifacts belong" table grew two rows: scripts/compliance/ (with runbook back-reference to runbooks/compliance-pdf-generation.md) and governance/ (firm legal record). reference/ row clarified to include vendor SOWs.
  • Cleanup: removed two stale local files that were never canonical — pwos-md-delta-2026-05-13.md (root) was an older draft superseded by the -merged version in strategy/; reference/blockskunk-phase-0-input-package.md was a byte-identical duplicate of the canonical at compliance/audits/blockskunk-phase-0-input-package.md.
  • CTO/CISO sole approval. No business-logic or compliance-posture change — provisioning of source artifacts already referenced from canonical docs, plus navigation-doc updates that catch up to today's new directories.

2026-05-14 (demo surfaces: Calendly URL alignment)

  • fix(pw-portal-v2): pwportal.app/demo now uses the same Calendly link as pwos.app/democxsk-7r5-qpm/protocol-wealth-investor (round-robin investor) → ct3f-pwd-qgm/protocol-wealth-team-discovery (team discovery). Per CTO/CISO 2026-05-13 follow-up to PR #51 to keep both demo surfaces routing through the same scheduling link. PR pw-portal-v2 #52 (single-line CONSULT_URL constant change with comment documenting the retirement).

2026-05-13 (pwos.app/demo + pwportal.app/demo public surfaces shipped)

  • feat(pw-os-v2 #324): pwos.app/demo public-facing showcase for OS-licensing prospects. New 8-section landing page with interactive EMF regime classifier, 8-check ticker selector (AAPL/MSFT/NVDA fixtures), chat IDE conversation replay with tool-call expand/collapse, append-only audit-log sample, 5-tier architecture cards, RIA-stack-vs-PWOS comparison table. Backend: 6 endpoints under /demo/api/* (regime, regime/list, ticker, ticker/:symbol, chat/history, audit/sample) reading 6 hand-curated JSON fixtures from apps/api/src/fixtures/demo/. Public + edge-cacheable (Cache-Control: public, max-age=60, s-maxage=300); router mounted before auth gates. All fixtures clearly fictional (Demo Advisor / [email protected]); audit-sanitization sweep test asserts zero @protocolwealthllc.com leaks. 1059 → 1070 api tests pass.
  • feat(pw-portal-v2 #50): pwportal.app/demo public-facing client showcase. Replaces the 2026-05-03 wireframe (PR #31) with a 7-section + sticky-banner client-prospect demo: portfolio dashboard (totals, allocation pie + bars, regime, advisor block), transparency (SVG line chart + benchmark table + itemized fee breakdown), 6 compliance protections with regulatory citations, 6-stage onboarding workflow with doc checklists, 5 communication channels. Cool-light theme (slate-50 page, white cards, brand teal primary) per pwportal-cool-light brand spec. Backend: 3 endpoints under /demo/api/* (portfolio, performance, onboarding-checklist) reading 3 fixtures. Persistent sticky "Demonstration only. All data is fictional" banner. 65 → 73 api tests pass.

2026-05-13 (chat export to Google Docs from chat header)

  • feat(pw-os-v2 #322): export advisor chat conversation to a Google Doc landing in PWOS_ADVISOR_DRAFTS_FOLDER_ID. CCO-prioritized at the 2026-05-13 investment meeting. New "Export" button in chat header → local preview modal → POST /api/advisor/chat-export/google-docs → DWD impersonation → Drive doc creation + body seed via docsBatchUpdate. Alternative Pattern (dedicated POST endpoint, NOT synthetic chat turn): the local-preview button-click is the consent gate; no two-turn confirmation flow polluting the transcript. Audit row keyed resource_type='drive_file' / resource_id=<file_id> so SEC export filters can pivot uniformly with the existing drive_create_doc pattern. New chat.export.created audit verb (3-segment registry shape; spec's chat_export.google_docs.created was canonicalized to fit the regex). Serializer at apps/api/src/lib/chat-export/serialize-to-markdown.ts: system messages excluded; PII placeholders preserved verbatim (export reflects what Claude saw); tool calls reconstructed from audit_log and folded into <details> blocks per assistant turn. 1033 → 1054 api tests pass (+21).

2026-05-13 (demo surfaces: pricing rewrite + Overview/Detailed toggle + Calendly links)

  • docs(demo): refine pricing model + add Overview/Detailed mode toggle + correct Calendly links on both /demo surfaces. Pricing changed from "retainer + profit interest" to "service-based retainer + per-engagement scope-up" (cleaner advisor-facing framing; pwos.app/demo only — pwportal.app/demo had no profit-interest text and is a no-op). Overview/Detailed toggle added to both surfaces with localStorage persistence (default: Overview). Calendly links wired: pwos.app/demo to team-discovery URL (ct3f-pwd-qgm); pwportal.app/demo to investor URL (cxsk-7r5-qpm; round-robin to an advisor). Toggle ARIA-labeled and keyboard-accessible; inactive variant is removed from the DOM (not CSS-hidden) so screen readers see only the active depth. Both PRs ship same-shape <ModeText overview="..." detailed="..." /> helper.
  • PRs: pw-os-v2 #326 (pwos.app/demo); pw-portal-v2 #51 (pwportal.app/demo).

2026-05-13 (pw-api Cloud Run image: pandoc + xelatex provisioning + /healthz/pdf)

  • feat(pw-api): install pandoc + xelatex on Cloud Run image for future server-side compliance PDF rendering capability. Closes deploy gate flagged in PR #325 (pw-os-v2 chat exports chose pdfkit specifically to avoid this Dockerfile change). Adds pandoc + texlive-xetex + texlive-fonts-recommended + texlive-latex-extra + fontconfig (plus wget for the existing HEALTHCHECK probe) to the runtime stage; estimated payload ~700MB, total image stays well under the 2GB ceiling. fc-cache -fv runs during build so the font cache is baked in. apt lists cleaned post-install.
  • Runtime base image change: node:24-alpinenode:24-bookworm-slim across all three stages (deps + build + runtime). Reason: the brief assumes Debian apt commands + Debian texlive packages, and the PW letterhead template (fontspec, Latin Modern, newunicodechar for σ / 5σ in the breach-notification template) is well-supported on Debian texlive — Alpine's texlive is leaner and would need per-package spelunking with no compatibility guarantee. All three stages on the same libc keeps native-binding dependencies (pg, etc.) consistent. The Trivy + npm/npx-stripping discipline carries over unchanged (Debian slim puts npm at the same paths).
  • New endpoint: GET /healthz/pdf on the pw-api service (api mode only). Returns { status, pandoc: { available, version, error }, xelatex: { available, version, error }, tools_available }. 200 when both tools resolve; 503 when either is missing. Result cached in-process for 5 minutes so deploy smoke + ops curl don't fork xelatex on every hit. Implementation at src/routes/healthz-pdf.ts (~110 LOC + 5 unit tests covering both-available / pandoc-missing / xelatex-missing / cache-hit / first-line-version-parsing).
  • Runbook update: shared/runbooks/compliance-pdf-generation.md v0.2 — new §8 "Cloud Run availability" documenting that the toolchain is installed but no endpoint invokes it yet; the future CCO-self-service POST /api/advisor/compliance/regenerate-pdf endpoint is sketched with auth + audit + allowlist + output-folder constraints but not yet built. Also explicitly notes why this is separate from the chat-export pdfkit path (different requirements: deterministic in-request rendering vs. full LaTeX letterhead).
  • No new business logic: this PR only provisions infrastructure capability. No new PDF endpoint is exposed; no compliance document is regenerated server-side; no chat-export flow changes. Endpoint design + auth posture for the future regeneration endpoint is a separate work item.
  • Deploy verification: post-merge, CI's deploy smoke step should hit GET /healthz/pdf and confirm 200 with valid pandoc + xelatex version strings. Cold-start latency target: <2s impact from the new layer (mostly Debian glibc swap; the apt-installed binaries are only forked when /healthz/pdf is hit, not at boot).
  • Full pw-api unit suite green post-change: 850 → 855 (+5 healthz-pdf tests). Lint + typecheck clean.
  • CTO/CISO sole approval. Infrastructure provisioning; no business logic; closes a flagged deploy gate.

2026-05-13 (root-level docs path cleanup from 2026-05-13 reorganization)

  • docs(shared): fix remaining stale path references from 2026-05-13 reorganization in root-level docs (AGENTS.md, PW-CHAT-CAPABILITIES, PW-ARCHITECTURE, PW-STATE). Move-only PR; no content changes. Historical CHANGELOG entries intentionally preserved as-is.
  • Path corrections applied (30 lines across 13 files, perfect insert/delete symmetry confirms zero content drift):
    • shared/api/<vendor>.mdshared/architecture/api/<vendor>.md (3 sites: AGENTS.md ×2, PW-STATE-2026-05-11.md, runbooks/postmark-inbound-branded-subdomain.md)
    • shared/PW-CHAT-TOOLS-AUDIT-2026-04-23.mdshared/architecture/chat-tools-audit/PW-CHAT-TOOLS-AUDIT-2026-04-23.md (7 sites: PW-CHAT-CAPABILITIES, PW-ARCHITECTURE, PW-STATE, architecture/BLUEPRINT, architecture/PW-CACHING-LAYER, strategy/pii-manifest-schema, strategy/ONBOARDING, plus DELTAS-2026-05-02 mirror)
    • shared/PW-CHAT-TOOLS-AUDIT-DELTAS-2026-05-02.mdshared/architecture/chat-tools-audit/PW-CHAT-TOOLS-AUDIT-DELTAS-2026-05-02.md (3 sites: PW-STATE, architecture/BLUEPRINT, compliance/reg-s-p/reg-s-p-compliance-record-2026)
    • shared/strategy/BLUEPRINT.mdshared/architecture/BLUEPRINT.md (5 sites in strategy/ONBOARDING.md)
    • shared/incident-response-plan.mdshared/compliance/posture/incident-response-plan.md (1 site in strategy/ONBOARDING.md)
    • shared/PW-SUBPROCESSORS-v1_1.mdshared/compliance/posture/pw-subprocessors-v1_1.md (3 sites: PW-STATE, strategy/ONBOARDING, compliance/wisp-compliance-hub-plan)
    • shared/PW-SUBPROCESSORS-*.md wildcard → resolved to canonical shared/compliance/posture/pw-subprocessors-v1_1.md (1 site in compliance/vendor-docs/README)
    • shared/PW-SECURITY-POSTURE-PARTNER-v1_1.mdshared/compliance/posture/pw-security-posture-partner-v1_1.md (2 sites: PW-ARCHITECTURE, PW-STATE, architecture/PW-CACHING-LAYER)
    • shared/PW-LEGACY-SERVICES-MIGRATION-PLAN-REVISED-2026-04-24.mdshared/status/PW-LEGACY-...md (1 site in PW-ARCHITECTURE)
    • shared/strategy/emf-canonical.mdshared/emf-canonical.md (1 site in PW-ARCHITECTURE — emf-canonical is at root, not in strategy/)
    • shared/strategy/blockskunk-phase-0-input-package.mdshared/compliance/audits/blockskunk-phase-0-input-package.md (1 site in compliance/reg-s-p/reg-s-p-compliance-record-2026)
  • Verification: post-edit scan shows zero stale references remain in non-archive, non-CHANGELOG, non-inbox, non-status (date-snapshot) files. Remaining hits live in archive/** (frozen), inbox/** (session captures), status/** (date-stamped revisions of state docs), compliance/changelog.md (historical log), compliance/decisions/PW-ONCHAIN-... (point-in-time decision record), compliance/cco-approvals/** (point-in-time approval records), strategy/CURRENT-STATE.md (date-stamped "As of" entries), and strategy/pwos-advisor-agent-discovery-pass-2.md (2026-05-09 discovery snapshot) — all intentionally preserved as historical artifacts.
  • CTO/CISO sole approval. Pure path cleanup; no content drift. Closes the dangling-reference follow-up flagged in CHANGELOG entry "compliance docs consolidation + archive + dangling-ref cleanup" (PR #65).

2026-05-13 (pw-os-v2 chat export to PDF + format selector)

  • feat(pw-os-v2): POST /api/advisor/chat-export/pdf lands. Sibling endpoint to the Google Docs path that shipped earlier today; same canonical serializer (lib/chat-export/serialize-to-markdown.ts), same chat.export.created audit verb. Two delivery modes: (a) application/pdf bytes returned with Content-Disposition: attachment; filename="PW-Chat-YYYY-MM-DD-…pdf" for browser download (default); (b) Postmark transactional email with the PDF as a base64 attachment when send_to_email: true + email_address.
  • PDF generation via pdfkit (already a runtime dep; sibling to apps/api/src/lib/pdf-generator.ts). New module apps/api/src/lib/chat-export/generate-pdf-from-markdown.ts parses the serializer's markdown back into structured turns + renders via PDFKit primitives. No pandoc / xelatex / texlive on the Cloud Run image — pdfkit is in-process. Footer per chat-export spec: advisor name + email + page numbers (NOT the CRD #335298 footer reserved for compliance documents). Disclaimer at the bottom: "This is a chat transcript export, not a personalized recommendation. See PW disclaimers at protocolwealthllc.com/disclosures."
  • Audit row shape (new resource_type='pdf_export'): actor_id = advisor.sub; resource_id = opaque pdf_<uuid>; afterState carries target_kind='pdf', conversation_id, file_name, length_chars, message_count, tool_call_count, delivered_via ('download' | 'postmark'), email_address (when delivered via Postmark), size_bytes, advisor_email. The Google Docs path keeps resource_type='drive_file' keyed by file_id. SEC examiners pivot on resource_id (audit-log search at pwos.app/admin/compliance/audit-log) to reconstruct any single export event end-to-end.
  • Frontend: the existing ChatExportPreview modal grew a format selector (PDF | Google Doc; PDF default per CCO-prioritized advisor preference). PDF branch adds an "Email PDF instead of download" checkbox + email input. Google Doc branch keeps the existing "PWOS Advisor Drafts" target. Download path uses Blob + URL.createObjectURL + synthetic anchor click (Safari-safe URL revoke on next tick). Success-state banners differentiate download / email / drive.
  • Audit doc delta: shared/architecture/chat-tools-audit/PW-CHAT-TOOLS-AUDIT-DELTAS-2026-05-13.md documents the Alternative Pattern rationale (dedicated POST endpoint, NOT a chat tool with a two-turn confirmation gate — the modal click is the consent gate, advisor is the direct actor, Claude isn't in the loop). 14-layer defense-in-depth mapping included; no new anti-list entries triggered.
  • Test coverage: new apps/api/src/lib/chat-export/generate-pdf-from-markdown.test.ts (14 tests: markdown parser + filename helper + PDF buffer shape with valid %PDF magic + 10-turn pagination + empty-turn handling); 8 new tests in apps/api/src/routes/chat-export.test.ts (download branch, email branch with Postmark attachment shape, audit row contents, PDF-generation failure → 500, Postmark failure → 502, validation rejections). Full suite 1059 → 1081 passing; lint clean.
  • CTO/CISO sole approval. No compliance posture change (audit-log + 7-year retention lock already cover chat-derived deliverables); same PII redaction posture as the chat surface (placeholders preserved verbatim — raw NPI never enters the export pipeline because it never enters the chat in the first place).

2026-05-13 (pw-os-v2 chat cache_control 4-marker fix + rolling assistant-end marker)

  • fix(pw-os-v2): chat 400 on multi-turn conversations with attachments. Anthropic caps cache_control breakpoints at 4 per request. lib/attachments.ts was emitting one marker per PDF/image block; system + last-tool consume 2 more; a 4-turn conversation with attachments hit 6 markers and returned invalid_request_error: A maximum of 4 blocks with cache_control may be provided. Found 6. Cache is a longest-prefix match, so per-block markers were wasted budget anyway — only the rightmost marker per "section" affects the cache hit point. buildContentBlocks now emits a single marker on the last content block when enableCaching: true.
  • feat(pw-os-v2): rolling assistant-end cache marker. Replaces the prior "most recent historical user-with-attachments" marker (the bug-fix shape) with a marker at the end of the most recent assistant message in history. The assistant message is converted from content: stringcontent: [{type:'text', text, cache_control:{type:'ephemeral'}}]; Anthropic SDK accepts both forms. Caches a strictly longer prefix (includes the assistant reply, often the largest single block) and works regardless of whether earlier user turns had attachments. Final 4-marker budget: 1 system + 1 last-tool + 1 last-assistant-end + 1 current-user, independent of turn count.
  • Test coverage: new apps/api/src/lib/attachments.test.ts (5 tests) pins single-marker placement, the no-cache path, the no-attachment path, and the regression shape (4 PDFs + caching = 1 marker, not 4). Full suite 1033 → 1059 passing.
  • Observability follow-up: 24–48h Langfuse trace observation window before deciding on the 1-hour ephemeral TTL (cache_control: {type:'ephemeral', ttl:'1h'} beta). The 25% cache-write premium needs to be justified by actual session-gap profile from cache_read_tokens distribution shifts toward the assistant turns.
  • CTO/CISO sole approval. Cache optimization on the chat surface; no compliance posture change; no client-data impact (chat was 400-ing pre-fix, no traffic ever reached Anthropic with the bad shape).

2026-05-13 (WISP AI posture consolidation + final navigation cleanup)

  • chore(shared): consolidate wisp-ai-posture into one canonical document and tighten navigation across shared/. The WISP AI posture was previously split across compliance/wisp-ai-posture-2026-04-30.md (v1.0, 194 lines of content) and compliance/wisp-ai-posture-2026-05-12.md (v1.1, a 60-line delta that referenced v1.0 for unchanged sections). This pass merges them into a single compliance/wisp-ai-posture.md (v1.2, self-contained, ~190 lines) with all v1.0 content plus v1.1's §1 bullet 6 ingress-posture correction and Marketing Rule §206(4)-1 row. Cross-document path references updated to post-2026-05-13-reorg locations; §248.30(a)(5) citation aligned to the SEC final-rule structure; §8 roadmap updated to reflect Compliance Hub Phase B + C as shipped 2026-05-08. v1.0 + v1.1 retired to archive/compliance/wisp-ai-posture-history/ with a per-bucket README documenting the supersession.
  • External cross-references updated to point to the new canonical: PWOS.md (§8.6 + document references), PW-STATE-2026-05-11.md (§AI governance + companion-docs list), architecture/BLUEPRINT.md (§4 HITL Tier 2 anchor + §5 AI surface footer reference), compliance/README.md (canonical-documents table row). The two ref-sites in compliance/decisions/PW-ONCHAIN-REPOSITIONING-DECISION-2026-05-12.md that mention the dated v1.1 filename remain unchanged because they are describing what happened on 2026-05-12 (historical record).
  • Root shared/CLAUDE.md + shared/README.md updates to fix the prior path inaccuracy: KYC/AML and WISP AI Posture live at compliance/ root, not compliance/docs/. compliance/docs/ is now explicitly documented as reserved for finalized program documents migrated from /mnt/project/ (Compliance Manual, Code of Ethics, ADV Part 3, etc.), with a placeholder README inside the directory listing the queued migrations and their CCO-led tracking surface.
  • compliance/changelog.md updated with the four cleanup waves shipped today (PR #62 reorg, #63 pipeline + first PDFs, #64 cross-doc reconciliation + KYC/AML v1.0 + Reg S-P Record 2026, #65 docs consolidation + archive + dangling-ref cleanup, and this WISP consolidation).
  • Net effect: every PW compliance document now exists in exactly one canonical location with the version, owner, and signed-PDF status visible at compliance/README.md. The root shared/CLAUDE.md and shared/README.md point readers to that index as the entry point for any compliance navigation question. Stale references to pre-reorg paths inside compliance/ are zero (verified via grep); the remaining instances live in archive/ and in CHANGELOG history entries, where they correctly describe a moment in time.

2026-05-13 (compliance docs consolidation + archive + dangling-ref cleanup)

  • chore(shared): consolidate compliance documentation; archive pre-pipeline PDFs; fix dangling references from the 2026-05-13 reorganization. Replaces the stale compliance/README.md (pointed to several pre-reorg paths that no longer exist) with a comprehensive document index listing the canonical version of every PW compliance document, its source markdown path, version, owner, and signed-PDF location. Adds a sibling compliance/reg-s-p/signed-pdfs/README.md documenting the version trail, DocuSign envelope workflow, and the recommended signing order for the three Reg S-P + AML artifacts in CCO queue. Adds archive/compliance/pre-pipeline-pdfs-2026-05-13/README.md documenting the four PDFs moved into archive: three user-hand-rendered pre-pipeline PDFs (compliance/kyc-aml-policy-v1_0.pdf, compliance/reg-s-p/customer-breach-notification-template.pdf, compliance/reg-s-p/reg-s-p-compliance-record-2026.pdf) and the superseded first-generation customer-breach-notification-template-v0_1.pdf. Each is preserved under archive with a -pre-pipeline suffix or its original versioned name for 7-year retention per Rule 17a-4 conservative posture.
  • Dangling-reference fixes within compliance/: five files updated.
    • compliance/governance-dashboard.md: four references to shared/incident-response-plan.mdshared/compliance/posture/incident-response-plan.md.
    • compliance/ciso-dashboard.md: two references to shared/incident-response-plan.mdshared/compliance/posture/incident-response-plan.md; "Related references" block updated to point to the actual current paths (posture/incident-response-plan.md, governance-dashboard.md, architecture/api/turnkey.md since the api/ subtree moved during the 2026-05-13 reorg) and to drop the non-existent phase-3-followups.md pointer (consolidated into the dashboards themselves).
    • compliance/wisp-ai-posture-2026-05-12.md: one reference to incident-response-plan.mdcompliance/posture/incident-response-plan.md.
    • compliance/posture/incident-response-plan.md: §10 (open gaps) and §10A.4 (planned automation) updated to point to the live governance-dashboard.md and ciso-dashboard.md "Open items" sections instead of the non-existent phase-3-followups.md tracker. IRP PDF regenerated through the pipeline (incident-response-plan-v1_2.pdf) to pick up the fix.
  • Dangling references outside compliance/ are noted as a follow-up. Several root-level shared docs (PWOS.md, PW-CHAT-CAPABILITIES-AND-ROADMAP-2026-05-01.md, PW-ARCHITECTURE-DEEP-DIVE-2026-04-24.md, AGENTS.md, PW-STATE-2026-05-11.md, CHANGELOG.md historical entries) still reference pre-reorg paths (shared/PW-CHAT-TOOLS-AUDIT-2026-04-23.md, shared/api/<vendor>.md, shared/strategy/BLUEPRINT.md, shared/incident-response-plan.md). Scope-out from this cleanup since those files are large authoritative docs maintained separately; they should be reconciled in a focused follow-up PR.
  • Net effect for navigation: opening compliance/README.md now gives a single-screen table of every canonical PW compliance document with its source markdown, version, owner, and signed PDF. No more clicking through stale relative links from the pre-reorg layout.

2026-05-13 (compliance docs cross-reconciliation + KYC/AML v1.0 + Reg S-P Compliance Record 2026)

  • feat(shared): cross-document reconciliation pass on the three Reg S-P + AML artifacts in CCO signing queue. All three documents (Customer Breach Notification Template, Reg S-P Compliance Record 2026, KYC/AML Policy v1.0) now share consistent: (a) IRP version reference (v1.0, matching the actual file); (b) sign-off block format (PW underscore-line pattern matching the KYC/AML §13 block, replacing the previous "Pending" table cells in the other two); (c) cross-references to companion documents; (d) §248.30 final-rule citation scheme.
  • Customer Breach Notification Template bumped v0.2 → v0.3. Change log entry documents the IRP-reference tightening. §8.1 Sign-off section converted from a table to an underscore-line signature block for DocuSign compatibility. End-of-document marker updated to v0.3.
  • Reg S-P Compliance Record 2026 bumped 2026.0 → 2026.0.1. §2.2 IRP version reference and §4 documentation table both tightened from "v1.x" to "v1.0". §4 documentation table line for KYC/AML Policy updated to reference compliance/kyc-aml-policy-v1_0.md (path was kyc-aml-policy.md) with the version "v1.0 under PW letterhead; pending CCO + CTO/CISO sign-off" replacing the prior "Pending CCO rebrand from prior Enclave letterhead" placeholder. §4 open items note for the KYC/AML rebrand updated to reflect that v1.0 is drafted. §8.1 Sign-off section converted from a table to an underscore-line signature block.
  • KYC/AML Policy v1.0 lands. First PW-headed version under SEC Reg S-P amended posture. Replaces the legacy Enclave Digital Wealth-headed version effective March 1, 2025 (signed CCO April 16, 2025); substantive AML framework preserved. Entity references updated; AML Compliance Person designation aligned with the Resolution of the Managers dated January 13, 2026 (DocuSign Envelope 9854DBBF-EB8C-47F1-8071-9D4063C65E47). §13 Approval block uses CCO's full title "Chief Compliance Officer / AML Compliance Person".
  • 3 PDFs in compliance/reg-s-p/signed-pdfs/ ready for CCO DocuSign workflow:
    • customer-breach-notification-template-v0_3.pdf (24 pp)
    • reg-s-p-compliance-record-2026-v2026_0_1.pdf (16 pp)
    • kyc-aml-policy-v1_0.pdf (24 pp)
  • Pipeline change: scripts/compliance/pw-letterhead.tex now loads amssymb so pandoc-emitted task-list checkboxes (- [ ]\item[$\square$]) render correctly. Required for the Reg S-P Compliance Record's §9 internal-validation checklist.
  • Pipeline usage discipline: documents that carry a native sign-off / approval section in the markdown body render WITHOUT --signature. The flag is reserved for documents that lack a native block. Avoids the duplicate-signature-block footgun where the template would append a generic "Chief Compliance Officer" block under a document whose native block already uses a stronger title (e.g., KYC/AML's "Chief Compliance Officer / AML Compliance Person").
  • Recommended signing order for CCO, per the audit-recommended chain: (1) KYC/AML Policy v1.0 — closes the Enclave-letterhead gap that the Reg S-P Compliance Record §4 inventory references; (2) Customer Breach Notification Template v0.3; (3) Reg S-P Compliance Record 2026.0.1 — signed last so its §4 documentation table reflects all upstream signings.

2026-05-13 (compliance PDF generation pipeline)

  • feat(shared): markdown → PDF pipeline for PW compliance documents. New scripts/compliance/generate-compliance-pdf.sh wraps pandoc + xelatex with a custom letterhead (scripts/compliance/pw-letterhead.tex). Output is DocuSign-ready: firm-name header, "SEC RIA • CRD #335298 • " footer, "Page X of Y" page numbers, auto table of contents for documents over ~1,750 words (≈ 5 pages), optional CCO + CTO/CISO acknowledgment block when --signature is passed. Title is auto-extracted from the first H1 of the source; effective date reads from YAML front-matter or defaults to today (UTC) + "(draft)". Internal markdown links are folded to " (see path/file.md)" so PDFs do not carry broken hyperlinks; HTML comments are stripped. Greek + math glyphs (σ, μ, α, β, δ, π) get a DejaVu Serif fallback so the IRP's "5σ" anomaly threshold renders correctly.
  • First batch shipped to compliance/reg-s-p/signed-pdfs/ (3 PDFs, 285 KB total):
    • customer-breach-notification-template-v0_1.pdf (24 pp) — Reg S-P §248.30(a)(4) customer-notification template for CCO review; signature block present.
    • incident-response-plan-v1_2.pdf (16 pp) — IRP v1.2 with signature block.
    • pw-subprocessors-v1_1.pdf (9 pp) — public-facing subprocessors list, classified "Public on protocolwealthllc.com/subprocessors", no signature (public-facing artifact).
  • Pipeline documented at runbooks/compliance-pdf-generation.md: install commands for WSL/Debian/Ubuntu + macOS, usage examples, naming convention (<basename>-v<MAJOR>_<MINOR>.pdf for drafts; -signed.pdf suffix for the DocuSign returned PDF), DocuSign envelope workflow (CCO → CTO/CISO → optional counsel), styling guidance, and the 7-year Rule 17a-4 retention posture (drafts + signed PDFs land in the same signed-pdfs/ folder and are git-committed alongside the source markdown).
  • Operational tooling, no business logic. CTO/CISO approval only. One-time system-deps install (pandoc texlive-xetex texlive-fonts-recommended texlive-latex-extra) is captured in the runbook; the pipeline preflights both binaries and emits a stable install hint when either is missing.
  • scripts/compliance/ is a new top-level directory in shared/. Justification: pipeline scripts depend on a co-located LaTeX template, so a single directory for the pair is clearer than splitting between bin/ (for the shell wrapper) and an ad-hoc template location. Future compliance tooling (e.g., signed-PDF audit-trail scripts) extends the same directory.

2026-05-13 (Reg S-P §248.30(a)(4) customer-notification template)

  • feat(shared): Customer Breach Notification Template v0.1 drafted for CCO review. New compliance/reg-s-p/customer-breach-notification-template.md (641 lines) is the pre-approved Reg S-P §248.30(a)(4) customer-notification artifact, pre-positioned for the smaller-entity compliance date of June 3, 2026. Eight required sections: Purpose; When notification is required (with the §248.30(d)(11) sensitive-customer-info table mapped to PW's actual data surface, the "reasonably likely" trigger criteria, investigation flow, full decision tree for the "no substantial harm or inconvenience" exception, and the §248.30(a)(5)(ii) 72-hour service-provider clause); Pre-Approved Notification Template (verbatim letterhead + body + signature block, with the six §248.30(b)(3) mandatory elements as a pre-send checklist); Identification of Affected Customers (SQL query template against audit_log); Delivery Mechanism (Postmark / Gmail-DWD primary, USPS Certified secondary, service-provider on-behalf-of clause); Documentation per incident (7-year retention per Rule 17a-4 conservative interpretation); Post-notification review (30-day post-incident review, quarterly CCO review, IRP update path); Approval cadence (joint CCO + CTO/CISO sign-off, quarterly review, incident-triggered unscheduled review). Appendix A cross-references each section to the IRP, Subprocessors v1.1, and Compliance Manual. Appendix B is the CCO pre-send checklist. Voice: PW-style (no em-dashes, semicolons where pause is needed, fiduciary-clear). Ready for CCO redline and DocuSign signature workflow.

2026-05-13 (shared/ directory reorganization)

  • docs(shared): reorganize shared/ into compliance/{docs,posture,audits,gap-maps,reg-s-p,decisions,vendor-correspondence}/ + architecture/ + strategy/ structure. Three new documents added: strategy/2026-05-13-investment-meeting-decisions.md (investment-meeting captures), compliance/audits/blockskunk-phase-0-input-package.md (BlockSkunk Phase 0 engagement input), compliance/gap-maps/2026-05-13-compliance-doc-gap-map.md (cross-validation compliance gap analysis with Reg S-P amended June 3, 2026 deadline focus). Move-only PR (no content changes); preserves git history via git mv. Root-level files relocated: PW-CHAT-TOOLS-AUDIT-2026-04-23.md + -DELTAS-2026-05-02.mdarchitecture/chat-tools-audit/; error-response-pattern.mdarchitecture/; incident-response-plan.mdcompliance/posture/; PW-SUBPROCESSORS-v1_1.mdcompliance/posture/pw-subprocessors-v1_1.md (lowercased); PW-SECURITY-POSTURE-PARTNER-v1_1.mdcompliance/posture/pw-security-posture-partner-v1_1.md (lowercased); api/* (28 files) → architecture/api/; strategy/BLUEPRINT.mdarchitecture/BLUEPRINT.md. New README.md (human navigation) + CLAUDE.md (agent navigation + organization principles) at shared/ root.

2026-05-13 (pw-os-v2 ScanError.provider enum sync)

  • feat(pw-os-v2): ScanError.provider enum extended to mirror today's pw-api fundamentals-chain providers. pw-api shipped 5 PRs today (#206 Marketstack, #208 EDGAR XBRL, #209 ifrs-full taxonomy, #210 ETF short-circuit) that added marketstack, edgar, twelvedata, and mboum_thin_etf as named provider attempt values in the /scan-bundle response shape. pw-os-v2's ScanError.provider was still 'mboum' | 'fmp' | 'both' | null — every error attributed to those new providers got null'd in the runner's error-display path. Type now reads 'mboum' | 'marketstack' | 'edgar' | 'twelvedata' | 'fmp' | 'multi' | 'mboum_thin_etf' | null; renamed 'both''multi' for accuracy now that ≥5 providers can be attempted.
  • providerFromBundleCode() pattern-match extended to recognize the new error-code prefixes: marketstack_ / ms_marketstack; edgar_edgar; twelvedata_ / td_twelvedata; mboum_thin_etf exact match → mboum_thin_etf (ETF short-circuit attribution); synthetic codes (both_providers_thin, both_providers_empty, mboum_thin_no_fallback) → multi.
  • Runner attribution semantic clarified: the runner now PREFERS the bundle's errorCode attribution over the providersAttempted flags. When errorCode='fmp_4xx_404' AND both mboum + fmp were 'miss', the ScanError gets provider='fmp' (the failing provider) rather than the older 'both' fallback. multi is reserved for the synthetic codes above. One existing test assertion updated to match the new semantic; behavior change is more accurate.
  • 9 new tests in scan_runner.test.ts pin every new provider prefix + the renamed multi value + the null-errorCode fallback path (1033 passing total, up from 1024). No migration required (TypeScript-only).
  • 'both''multi' rename is internal to pw-os-v2. pw-api's error_codes.both_providers_thin synthetic code stays as-is; the runner's pattern-match handles the legacy spelling. Downstream consumers (dashboards, audit_log writes) that read ScanError.provider are TypeScript-typed and the compiler caught the one site that referenced 'both' directly.

2026-05-13 (pw-api ETF short-circuit ahead of EDGAR)

  • feat(pw-api): ETF classifier short-circuits EDGAR for fund/ETF tickers when MBOUM is thin. Closes the second of two coverage gaps surfaced by today's validation scan (correlation_id 2c9dfb94-96b1-44ca-ac31-b8e8506b6ecc): POWR (Invesco DWA Power Sector ETF) + SHLD (Direxion Daily Healthcare Bull 2X Shares, historical) fired edgar_fundamentals.ticker_no_cik because ETFs aren't in SEC's company_tickers.json (operating-company registry); they're in company_tickers_mf.json (funds registry) and even with CIK resolution their XBRL filings (N-1A / N-CSR) carry no us-gaap or ifrs-full fundamentals concepts. New module src/providers/edgar_xbrl/etf_classifier.ts exposes isLikelyETF(ticker) + classifyTickerType(ticker, tickerMap?, fundsMap?) with a hardcoded allowlist (~200 entries — SPDRs, Vanguard, iShares, Invesco, ARK, Direxion / ProShares leveraged products, Schwab, commodity ETFs, including POWR + SHLD specifically) and a conservative pattern matcher (Direxion-style prefixes). False positives mitigated via per-ticker allowlist verification; false negatives flow through the normal chain. Wired ahead of EDGAR in scan-data.ts: when MBOUM is thin AND the classifier fires, EDGAR + Twelve Data + FMP all skip cleanly. Bundle surfaces data_source='mboum_thin_etf' (new distinct enum value, separate from 'unavailable') + providers_attempted.edgar='skipped_etf' (new union member) + etf_skip: boolean (per-bundle signal letting the runner aggregate scanMetadata.etf_skip_count). Structured logs: etf_classifier.classified (per-classification, etf/fund only — operating companies don't log to avoid noise) + scan_data.etf_skip_edgar (chain-level, with reason = allowlist / pattern / sec_funds_map / sec_operating_map source). EMF scoring treats mboum_thin_etf as unscoreable — ETFs are categorically out of EMF's universe.

  • Future extension flagged: lazy-load company_tickers_mf.json (~500KB) as a tertiary signal once allowlist maintenance shows wear. v1 covers PW's universe; the funds-map path is reserved.

  • Test coverage: 26 new tests across test/unit/etf-classifier.spec.ts (allowlist hits, pattern matching, case insensitivity, foreign ADRs correctly classified as operating-company, unknown fallthrough, logEtfClassification noise discipline) and test/unit/scan-data-etf-shortcircuit.spec.ts (MBOUM-thin + ETF short-circuit path; MBOUM-hit + ETF normal path; operating-company + MBOUM-thin → EDGAR runs normally; etf_skip per-bundle signal for runner aggregation). Two existing tests in scan-data-twelvedata.spec.ts re-tickered from SHLD/POWR → OKLO/CMPS (operating companies) so chain-logic pinning isn't entangled with classifier behavior. Full suite: 812 → 838 passing.

  • Cross-repo references: pw-api CLAUDE.md + README.md note the ETF short-circuit ahead of the provider chain. shared/api/edgar.md unchanged (the short-circuit is upstream of EDGAR; no taxonomy or endpoint changes).


2026-05-13 (pw-api EDGAR XBRL ifrs-full taxonomy extension)

  • feat(pw-api): EDGAR XBRL concept mapping extended with ifrs-full taxonomy support. Closes a coverage gap surfaced by today's validation scan (correlation_id 2c9dfb94-96b1-44ca-ac31-b8e8506b6ecc): EDGAR returned 200 for CMPS (COMPASS Pathways plc, UK-domiciled biotech, CIK 0001816696) but concept_mapping.ts extracted zero fields because every tag in its priority lists was us-gaap-only — CMPS reports under ifrs-full as a 20-F foreign private issuer. TAG_PRIORITY now keyed by taxonomy: { 'us-gaap': [...], 'ifrs-full': [...] } per ScanBundle field. Resolver walks us-gaap first (priority preserved); on whole-list miss falls through to ifrs-full. Mappings seeded with the closest IASB-IFRS-Taxonomy equivalents per field: Revenue / RevenueFromContractsWithCustomers (revenue), ProfitLoss / ComprehensiveIncomeLoss (net_income), Assets (total_assets), CurrentAssets (current_assets), CurrentLiabilities (current_liabilities), NoncurrentBorrowings / LongTermBorrowings (long_term_debt), Equity / EquityAttributableToOwnersOfParent (shareholders_equity), NumberOfSharesIssued / NumberOfSharesOutstanding (shares_outstanding), CashAndCashEquivalents / Cash (cash_and_equiv), CashFlowsFromUsedInOperatingActivities (operating_cash_flow), PurchaseOfPropertyPlantAndEquipment (capex). FCF stays derived (ocf - |capex|); total_debt component sum keyed on whichever taxonomy resolved long_term_debt (us-gaap: LongTermDebt* + DebtCurrent + ShortTermBorrowings + CommercialPaper; ifrs-full: NoncurrentBorrowings + CurrentBorrowings + LongTermBorrowings).

  • New response field + log signals. ScanBundleResponse envelope adds edgar_taxonomy_used: 'us-gaap' | 'ifrs-full' | 'mixed' | null populated from ConceptExtractionResult.taxonomy_used; null when EDGAR wasn't attempted, otherwise reflects the dominant taxonomy across resolved fields ('mixed' for rare hybrid filers). New structured log edgar_concept_mapping.taxonomy_used fires per-ticker on EDGAR hit with {ticker, taxonomy_used, concepts_resolved_count, correlation_id} so ops can pivot on the us-gaap / ifrs-full distribution at a glance. Existing edgar_concept_mapping.miss log payload extended additively with tried_taxonomies: ['us-gaap', 'ifrs-full'] + map-shaped tried_tags: { 'us-gaap': string[]; 'ifrs-full': string[] }. tags_used shape on the extractor result changed from Record<string, string | null>Record<string, { taxonomy, tag } | null> to carry the winning taxonomy alongside the tag name.

  • Tests: 12 new (test/unit/edgar-concept-mapping-ifrs-full.spec.ts covering CMPS-shaped ifrs-full happy path, hybrid filers with both taxonomies present, ifrs-full tag-fallback chains, miss case with both taxonomies tried, total_debt component sums per taxonomy; test/unit/scan-data-cmps.spec.ts covering the end-to-end CMPS regression with mocked MBOUM-thin + EDGAR-ifrs-full-hit). Existing edgar-concept-mapping.spec.ts us-gaap pinning preserved (3 assertions adjusted to the new tags_used map shape). scan-data-edgar.spec.ts extended with edgar_taxonomy_used assertions. Full suite: 812 → 824 passing.

  • Cross-repo references: shared/api/edgar.md XBRL section extended to enumerate the three taxonomies (us-gaap + ifrs-full + dei) and the per-field IFRS analogues. pw-api CLAUDE.md + README.md note dual-taxonomy coverage.


2026-05-13 (pw-api EDGAR XBRL fundamentals fallback)

  • feat(pw-api): SEC EDGAR XBRL wired as canonical fundamentals fallback in /scan-bundle. Extends the existing EDGAR client (src/lib/edgar.ts) with fetchCompanyFacts + fetchCompanyConcept + fetchFrames covering SEC's /api/xbrl/companyfacts/CIK*.json, /companyconcept/.../taxonomy/tag.json, and /frames/taxonomy/tag/unit/CY*.json endpoints. Free, ToS-clean, no credit budgets, no API key. New module src/providers/edgar_xbrl/concept_mapping.ts walks a tag-priority list per ScanBundle field (post-ASC-606 RevenueFromContractWithCustomerExcludingAssessedTax → pre-606 Revenues → deprecated SalesRevenueNet, etc.), filters to fp='FY' annual values, collapses 10-K/10-K/A duplicates by latest filed date, and returns current + prior periods with full ScanBundlePeriod coverage (revenue, net_income, gross_profit, assets, equity, OCF, capex, FCF=derived, total_debt=summed). Inserted into the scan-data.ts fallback chain as Step 4 between Marketstack (Step 3) and Twelve Data (Step 5). Gated by EDGAR_FUNDAMENTALS_ENABLED env var (default false; flip post-deploy via gcloud run services update). Process-singleton 8 req/sec rolling-window throttle applied to all four EDGAR fetch paths (20% headroom under SEC's 10/sec hard cap). data_source enum extends to include 'edgar'; providers_attempted + error_codes envelopes gain edgar keys (additive — older readers projecting {mboum, fmp} keep working); new response field edgar_concepts_resolved (number | null) lets the runner aggregate scanMetadata.apiCallCounts.edgar_concepts_resolved over a scan. Structured logs: edgar_fundamentals.attempt / .result (parity with twelvedata_fallback.* / marketstack_fallback.* shapes), edgar_fundamentals.disabled (flag-off path), edgar_fundamentals.ticker_no_cik (non-US listings, crypto, ADRs without SEC filings), edgar_concept_mapping.miss (per-field tag-priority exhaustion — surfaces unmapped concepts for future mapping refinement).

  • Twelve Data downgraded to quaternary, flag-off in prod. Validated 2026-05-13 that Twelve Data free Basic tier does NOT include fundamentals endpoints — every /income_statement, /balance_sheet, /cash_flow request returns 403 "available exclusively with pro or ultra or venture or enterprise plans" regardless of ticker. TWELVEDATA_FALLBACK_ENABLED=false set on Cloud Run; code retained in case of a future Pro-tier upgrade. EDGAR XBRL replaces it as the canonical fundamentals path.

  • New /v1/internal/edgar/* endpoints. Three new routes: GET /edgar/company-facts?ticker=X (or cik=), GET /edgar/concept?ticker=X&taxonomy=us-gaap&tag=Y, GET /edgar/frames?taxonomy=us-gaap&tag=Y&unit=USD&period=CY2024Q4. All match the existing /edgar/submissions Zod-validated + {error, kind} envelope pattern. Endpoint catalog grew from ~38 to ~41.

  • Cross-repo references: updated shared/api/edgar.md with the XBRL surface section (endpoints + concept-mapping notes + rate-limit posture + drift-history entry); pw-api CLAUDE.md + README.md reflect the new providers + chain + endpoint count.


2026-05-13 (pw-os-v2 audit_log schema fix)

  • fix(pw-os-v2): audit_log.resource_id widened UUID → TEXT (migration 0053). Every ai.tool.called insert since the chat-tool audit path shipped has been silently failing on the SQL layer with invalid input syntax for type uuid: "<tool_name>". Discovered 2026-05-13 via Cloud Logging audit — jsonPayload.action="tool.audit.write_failed" shows the failure across every registered tool (create_google_task, get_market_movers, drive_search, gmail_search, list_calendar_events, find_wealthbox_contacts, list_wealthbox_notes, list_wealthbox_tasks, get_fred_series, get_eia_dataset, list_covered_vaults, get_onchain_wallets, get_market_scan, list_google_task_lists, list_google_tasks). Root cause: migration 0015 typed the column UUID; tool-audit.ts writes snake_case tool names + Drive file IDs there. tool-audit.ts catches the error and logs tool.audit.write_failed but doesn't break the chat flow, so the schema gap was invisible. Pre-production discovery; no client data; no compliance disclosure required. Schema change widens audit_log.resource_id UUID → TEXT (preserves index idx_audit_log_resource with same partial-index predicate); same widening on audit_anomaly_findings.resource_id for type-compat with the audit_log rows it mirrors; write_audit_log() regenerated with p_resource_id TEXT (preserves migration 0048's p_correlation_id/p_trace_id DEFAULT NULL parameters). Reconstructive audit path for the affected window: tool.audit.write_failed Cloud Logging events (captures tool name + request_id + error), conversations + messages tables in pw-os Postgres, ai_audit_log parallel chat-turn record.

  • pw-api parallel schema flagged for follow-up. pw-api's audit_log.resource_id is also UUID-typed (migration 0001_foundation, reinforced in 0017 + 0027). All pw-api callers today pass domain UUIDs (profileRow.id, row.id, input.clientId, etc.) so no insert is failing today, but the column should be widened to TEXT in a sibling pw-api PR for cross-repo posture parity and future-safety. Not blocking; separate PR.

  • fix(pw-api): audit_log.resource_id widened UUID → TEXT (migration 0038) for architectural parity with pw-os-v2 #320. Closes the schema-parity gap flagged above on the same day. Two pw-api-specific divergences from pw-os-v2's 0053: (1) resource_id stays NOT NULL (no pre-auth event path exists in pw-api today; all callers pass a value); (2) idx_audit_log_resource is rebuilt WITHOUT the WHERE resource_id IS NOT NULL partial-index predicate (no-op when the column is NOT NULL anyway — matches 0001_foundation's original index definition). write_audit_log() regenerated with p_resource_id TEXT; preserves the 10-positional-arg tenant_id-first signature from 0027 (no correlation_id / trace_id parameters — those are pw-os-v2-only additions from 0047/0048). Drizzle schema mirror (src/db/schema/audit_log.ts) flipped to text('resource_id').notNull(). /v1/internal/admin/audit-trail endpoint Zod schema relaxed from z.string().uuid() to z.string().min(1).max(255) and SQL WHERE dropped the ::uuid cast. Schema-drift integration test (test/integration/schema-drift.spec.ts) extended to assert TEXT column type + p_resource_id text in the function signature; new dedicated regression file (test/integration/audit-log-resource-id-text.spec.ts) pins the snake_case / Drive-file-id / UUID-string / NOT NULL contracts end-to-end. No active failures in pw-api at fix time — this is purely architectural-parity / future-safety work.


2026-05-12 (chore session)

  • fix: pw-os scaling pinned in terraform (cloud-run-services.tf:237-238) — prevents future apply-drift of the 2026-05-12 min=1/max=2 policy. Closes the unexpected finding surfaced by Task 3's cloud-run-baseline apply, which silently reverted the 12:30 UTC gcloud hotfix back to min=0/max=10 on revision pw-os-00270-8ss. Restored via gcloud (pw-os-00271-9xx) + terraform now matches policy (pw-infrastructure #178). Same drift pattern as memory feedback_terraform_env_var_drift, just applied to scaling rather than env vars.

  • Pre-cron hardening for 2026-05-13 scheduled runs. Three concrete fixes + two verifications closed tonight rather than carrying into tomorrow's 09:00 UTC market scan + 13:00 UTC posture probe. pw-infrastructure #175 (PAT trigger fix on cloudflare-posture-probe.ymlsecrets.PW_POSTURE_PROBE_PAT || secrets.GITHUB_TOKEN fallback for gh pr create + gh pr merge so bot PRs trigger required-check workflows). pw-infrastructure #176 (terraform import { ... } block adopting pwos-market-scan-run-next Cloud Scheduler job into state — sibling of pwos-market-scan-tier1-daily, matches deployed state exactly so plan is zero-drift after the first apply). Verification: pw-os-v2 + pw-api required_status_checks.contexts both clean of any Analyze (*) entries; pw-infra cleaned by CTO/CISO after my earlier report flagged the self-block. Pre-flight: both scheduler jobs ENABLED, pw-os on revision 00269-hlg with worker_mode=scheduler_tick, no stranded queued jobs from today's run (Cloud Logging shows 1 queued + 1 succeeded for 2026-05-12, both job_id 3ae7534b-...).

  • Pending operator action: create fine-grained PAT PW_POSTURE_PROBE_PAT (scope: pull_requests:write + contents:write on Protocol-Wealth/pw-infrastructure only) and add as repo secret before tomorrow's 13:00 UTC scheduled run, otherwise the workflow falls back to GITHUB_TOKEN gracefully and the daily snapshot PR still opens but needs close+reopen to auto-merge. Full procedure in pw-infrastructure #175 body.


2026-05-12 (pw-onchain absorption — Slice 1 + 1.5 + 2 + 3)

  • pw-onchain → pw-api absorption: first read endpoint live. Slice 1 (pw-api #1897f8837f, merged 17:05:47Z) flipped /v1/internal/onchain/wallets from a 503 migration stub to a real RLS-scoped handler reading the wallets table in pw-core, with app-layer WHERE tenant_id = $X belt-and-suspenders on top of FORCE RLS (CI's POSTGRES_USER=pwapi is a superuser that bypasses FORCE RLS; production Cloud SQL IAM role is non-superuser). Also added src/lib/trackers/base.ts (type-only port of pw-onchain's tracker shapes) and src/lib/retry.ts (withRetry helper with tenacity-equivalent exponential backoff; throws on full exhaustion to preserve the silent-$0-snapshot regression-class fix). New canonical audit action onchain.wallets.listed. Slice 1.5 (pw-os-v2 #3156d785505, merged 17:24:11Z) patched the get_onchain_wallets chat tool to forward tenant_id=SYSTEM_TENANT_ID into the bridge call, narrowed OnchainWallet.wallet_id from numberstring, and updated the is_tracking description. Closes the single-window UX regression (400 tenant_id_required) created by Slice 1's tenant-scoping requirement. Wallets table is empty across all tenants today — endpoint returns {wallets: []} until a later slice seeds it from pw-onchain. Sidecar dep-fix PR #190 forced protobufjs ^8.0.2 via both overrides and pnpm.overrides to clear two same-day HIGH advisories pulled through @opentelemetry/[email protected]. Playbook updated at pw-onchain/docs/MIGRATION-TO-PW-API.md §9 (commits fc79cf0 + bbe5e10).

  • Slice 2 — EVM tracker port + first real data delivery. pw-api #195 (51a846bd, merged 18:30:34Z) flipped /v1/internal/onchain/portfolio/summary from 503 stub to a real handler wrapping a faithful TS port of pw-onchain/backend/app/services/trackers/evm.py (~700 LOC). Advisor-chat's get_onchain_portfolio tool now returns DeBank-sourced balances + DeFi positions instead of the migration notice. 3-source fallback chain (DeBank primary → Blockscout v2 → Etherscan/BaseScan native ETH); Alchemy skipped (not in pw-onchain source-of-truth either; deferred to slice 2.x if Blockscout coverage proves insufficient). Promise.allSettled tracker fan-out handles partial success per chain — one chain's RPC outage doesn't poison others. ETH spot pricing via free coins.llama.fi/prices/current/ (DefiLlama wraps CoinGecko, no new API key — extends existing src/lib/defillama.ts). New canonical audit action onchain.portfolio.summary_requested. 16 new EVMTracker unit tests + 13 new integration tests (29 total). RLS posture: new integration test pins audit-row SELECT under pwapi_app non-superuser role (CI-hardening PRs #193/#194 landed during Slice 2 design window). Two CI-surfaced fixes during the PR: audit_log.resource_id is UUID-typed (switched from wallet_address hex → tenant_id); PII scanner redacts crypto addresses in after_state JSONB (audit-row tests query by request_id instead — SEC-exam pivot path goes through audit_redaction_manifests per Reg S-P §248.30(b)). Simplified spam filter (DeBank is_spam + dust + is_core=false); full token_blocklist.py port (~629 LOC) deferred to a slice-X data-quality follow-up. Playbook §9 updated at pw-onchain/docs/MIGRATION-TO-PW-API.md (commit 19370f9).

  • Slice 3.5 — snapshot self-healing writer cron + nightly Cloud Scheduler job. pw-api #200 (companion: pw-infrastructure #179) ships the gap-fill writer for the daily_snapshot_corrections overlay table that Slice 3 introduced. src/lib/snapshot-self-healing.ts walks recent daily_snapshots rows, refetches DeBank user/total_balance (Source A) + DeBank-tokens-summed-via-DefiLlama-prices (Source B), and UPSERTs a correction row when sources diverge past SELF_HEAL_DRIFT_TOLERANCE_PCT (default 5%) with corrected_total_value = median(snap, A, B). New endpoint POST /v1/internal/cron/snapshot-self-healing/run-next (OIDC-only, cursor-resumable, bounded per-tick by SELF_HEAL_MAX_DURATION_MS racing Promise.race). Cloud Scheduler pw-api-snapshot-self-healing-nightly fires 0 4 * * * UTC, modeled on pw-api-cache-bust-summary (no app-layer second factor — Cloud Run IAM gate is the entire auth boundary on pw-api). New canonical audit action onchain.snapshot_correction.written; resource_id = tenant_id (UUID-typed); wallet_address rides in after_state JSONB redacted to <CRYPTO_ADDRESS_N>. 12 unit + 15 integration tests (27 new). Boundary: gap-fill writer ONLY — multi-source reconciliation against Octav, BTC UTXO walk reconstruction, LP NFT historical capture all stay in legacy pw-onchain per the 2026-05-12 boundary decision (compliance/decisions/PW-ONCHAIN-REPOSITIONING-DECISION-2026-05-12.md §3). No migration 0038 needed — Slice 3's 0037 had notes/source/drift_pct columns covering the writer's correction shape. Closes the absorption thesis at Slices 1-3 + 3.5.

  • Slice 3 — daily_snapshot_corrections migration + portfolio/performance overlay reader. pw-api #198 (bc86e06, merged 19:10:34Z) flipped /v1/internal/onchain/portfolio/performance from 503 stub to a real handler wrapping a faithful TS port of pw-onchain/backend/app/services/snapshot_aggregation_service.py. Migration 0037_daily_snapshot_corrections.sql ports pw-onchain 20260428_0043 with three documented divergences (§B tenant_id added + FORCE RLS + tenant_isolation policy, §C CHECK-enforced lowercase 40-hex when entity_type='wallet', §D UPSERT semantics via UNIQUE (tenant_id, entity_type, entity_id, snapshot_date) — derived data, re-runnable). The "original observation" invariant lives at the daily_snapshots layer (immutable raw EOD); corrections are a regenerable overlay. Overlay reader (src/lib/snapshot-aggregation.ts, ~250 LOC) faithfully ports merge rules: correction's total_value wins; holdings/positions fall back to raw or corrected_total_value for synthetic correction-only dates (pre-tracking history); is_corrected/is_synthetic flags surface in API. Endpoint shape: ?tenant_id=&wallet_address=&start_date=&end_date=&chains=; chains param ACCEPTED but UNUSED (chains_supported: false — daily_snapshots is wallet-level, not per-chain). New canonical audit action onchain.portfolio.performance_requested. 10 unit + 15 integration tests (25 new). RLS: route doesn't directly read tenant-scoped tables in handler, but overlay reader clauses WHERE tenant_id = $X belt-and-suspenders + new integration test pins cross-tenant isolation under pwapi_app non-superuser role. Single CI-surfaced fix: daily_snapshots.holdings_count/positions_count are INTEGER NOT NULL — integration seed helper updated to supply 0 defaults. Writer side (snapshot_self_healing) deferred to Slice 3.5 — corrections table is empty in production until the writer lands; route returns base data with corrections_applied: 0. Admin endpoint for back-office corrections has no port (pw-onchain doesn't have one either). Playbook §9 updated at pw-onchain/docs/MIGRATION-TO-PW-API.md (commit 0edd36f).


2026-05-12 (CI hardening)

Theme: GHAS CodeQL default-setup disabled on the three active runtime repos after a systemic stuck-aggregator failure mode blocked three PRs in a single day. CTO/CISO operational decision documented per Rule 206(4)-7.

  • Failure mode observed (2026-05-12). GitHub Advanced Security's CodeQL aggregator check_runs (the meta "CodeQL" status that combines individual Analyze job results into a single required check) sat indefinitely in queued state on three PRs across the active estate — pw-api #188 (>30 min before admin-merge), pw-infrastructure #173 (>27 min before admin-merge), and was about to block the Cloudflare-posture-probe bot PR (#174) flow on its first validation run. pw-os-v2 #314 escaped only by luck of timing (the aggregator happened to resolve in ~3s on that head SHA — same fast path that PR 172 took yesterday). Underlying Analyze (actions) / Analyze (javascript-typescript) jobs ALL succeeded on every PR — the failure is purely in the aggregator-to-check-run plumbing.

  • Action taken. GHAS CodeQL default-setup disabled on pw-os-v2, pw-api, and pw-infrastructure. Required-status-check CodeQL removed from main branch protection on the same three repos. Custom-workflow Analyze (actions) + Analyze (javascript-typescript) jobs (defined in each repo's .github/workflows/codeql.yml) continue running on every PR — they just aren't required for merge any more. For pw-infrastructure specifically, Analyze (actions) was promoted to the required-checks list (replacing the removed CodeQL aggregator) so the underlying CodeQL coverage is still gating merges; the difference is the required check now points to the actual workflow job that always reports, not the broken aggregator.

  • Net effect.

    • Same CodeQL static-analysis coverage on every PR.
    • No more systemic GHAS aggregator failures blocking merges.
    • Auto-merge actually fires on green CI (the original PW workflow design assumption).
    • SARIF results still flow into the GitHub Security tab from the Analyze jobs' upload-sarif step; the Code Scanning UI continues to surface findings without the aggregator layer.
  • Authority. CTO/CISO operational decision (CTO/CISO). Documented in shared/compliance/access-control-register.md 2026-05-12 entry. SEC RIA cybersecurity program (Rule 206(4)-7) attestation: change reduces operational fragility without weakening the detection layer; the "Detect" function of NIST CSF for source-code SAST is preserved (Analyze jobs still run, SARIF still uploads, alerts still gate where needed for advisory rather than blocking).

  • Tooling follow-up surfaced. Bot-authored PRs (e.g. the new cloudflare-posture-probe.yml daily snapshot PR pattern) created with the default GITHUB_TOKEN don't trigger downstream pull_request workflows — meaning the terraform-skip-non-terraform-prs.yml check emitters don't fire. Auto-merge then sits forever waiting for required terraform-named checks. Decision pending on whether to add a fine-grained PAT secret to pw-infra for the snapshot workflow vs. switching the trigger model. Tracked under the same hardening pass.


2026-05-12 (late)

Theme: Sibling PRs after PR 8.4 — pw-api PR 186 (error_code disambiguation) + pw-os-v2 PR 8.5 (scheduler-driven worker tick + SIGTERM, retires the in-process poller).

  • PR 186 (pw-api fix/pr186-scan-data-error-code-disambiguation). routes/scan-data.ts adds error_codes: { mboum, fmp } | null so callers can distinguish "MBOUM thin + FMP cascade not triggered" from "MBOUM thin + FMP 401" from "both providers thin." Synthetic top-level error_code='both_providers_thin' when both come back 2xx-but-thin; otherwise the top-level prefers the more-informative (non-thin) code. New HTTP-status-aware codes (mboum_4xx_404, fmp_5xx_500, fmp_4xx_429, ...) replace the prior kind-keyed names. Structured log scan_data.both_providers_thin fires only on the thin-thin branch. 7 new unit tests + full pw-api suite (645 tests) pass. Closes the ambiguity that today's 4 small-cap equity failures (CMPS, OKLO, POWR, SHLD) surfaced.

  • PR 8.5 (pw-os-v2 feat/pr85-scheduler-driven-worker-tick). Retires the in-process market_scan_worker.ts poller introduced by PR 8.3. New POST /api/internal/scheduler/market-scan/run-next claims one queued job (FOR UPDATE SKIP LOCKED) + awaits runMarketScan inline + transitions on success/failure. Hard per-tick timeout via env RUN_NEXT_MAX_DURATION_MS (default 9 min; row marked failed with error.code='tick_timeout' on overflow). Orphan-recovery sweep runs on every tick (10-min threshold, same as PR 8.3). SIGTERM/SIGINT handler added so Cloud Run scale-downs close the HTTP server cleanly. Boot log emits pw_os.boot with worker_mode='scheduler_tick' so dashboards can detect a rollback. startMarketScanWorker boot call removed from apps/api/src/index.ts; the worker module stays in tree for a 48-hour soak window before deletion. New bootstrap script apps/api/scripts/setup-run-next-scheduler.sh creates the Cloud Scheduler pwos-market-scan-run-next job (*/2 9-10 * * * UTC; OIDC + X-Scheduler-Token; retry=2/min=30s/max=120s); follow-up pw-infrastructure PR adopts it via terraform import. 7 new route tests + full pw-os-v2 suite (966 tests) pass.

  • Architectural note: min-instances=1 stays. The 2026-05-12 hotfix set pw-os min/max=1/2; PR 8.5 does not roll min-instances back to 0. The architectural correctness here is "scheduler-driven worker tick is the right pattern for Cloud Run regardless of min-instances setting"; min=1 stays in effect for warm-container latency + headroom for upcoming client portal traffic + future background work.

  • Follow-ups queued. (a) Delete lib/scanning/market_scan_worker.ts after 48h soak; (b) move Cloud Scheduler job into pw-infrastructure/terraform/cloud-run-baseline/scheduler-jobs.tf (sibling of pwos_market_scan_tier1_daily); (c) PR 7.7 BLUEPRINT.md codification of the three engineering rules surfaced by PR 8 series (async pattern, force-bypass idempotency, empirical pre-verification — per memory project_pr77_blueprint_engineering_rules).


2026-05-12

Theme: PR 8.3 09:00 UTC cron miss → root cause → hotfix → PR 8.4 (ETF routing + FMP observability). Ops hardening + access-control housekeeping.

  • PR 8.3 09:00 UTC cron failure — root cause. Cloud Run scaled pw-os to zero overnight after 23:09 UTC lifetime_cap_reached on 05-11; in-process worker died with the instance. Scheduler POST returned 202 in 126ms; instance idled out before the worker's 30s poll could claim job. Async pattern shipped in PR 8.3 is sound; min-instances=0 was the regression vector.

  • Hotfix 12:30 UTC. gcloud run services update pw-os --min-instances=1 --max-instances=2 --region=us-central1 --project=pwllc-prod. Worker booted on revision 00266; claimed job 3ae7534b-edc2-485d-8267-70756fd577c7 at 12:35:54 UTC; scan succeeded 12:39:45 UTC; wrote gs://pwllc-pwos-attachments/market-scans/2026-05-12.json. Revision pw-os-00267-hf4 current after max-instances update.

  • Diagnostic finding. Scan scored 71/118 with 47 fundamentals_missing_both_providers errors. Partition: 43 ETFs (correctly tagged assetType='etf') + 4 small-cap equities (CMPS, OKLO, POWR, SHLD, assetType='stock'). scan_runner.ts called fetchScanBundle on every row regardless of assetType despite layer_classifier.ts and regime_classifier.ts already branching on it.

  • PR 313 (PR 8.4) opened on pw-os-v2. ETF asset-class routing in scan_runner.ts + FMP fallback observability counter rename + structured log scan_runner.mboum_thin. Three commits: 6244ac0 type extension, 2c11b54 Fix 8.4-A, b3d1db1 Fix 8.4-B + preflight script. 959 tests pass; lint + typecheck clean. Expected post-deploy: tickerCountScored 71→114 (60% → 96.6%).

  • Sibling ticket: pw-api PR 186. routes/scan-data.ts:284 error_code disambiguation (coalesces mboumErr ?? fmpErr so both-providers-thin always reads as MBOUM thin). Not coupled to PR 313 deploy.

  • Ops hardening. Log-based metric market_scan_succeeded created via gcloud logging metrics create; GCS bucket pwllc-pwos-attachments set to retention=6y + versioning=on (Reg S-P §248.30(b) + Rule 204-2 books-and-records).

  • Deferred to PR 8.5. In-process worker → scheduler-driven tick endpoint architecture rewrite; SIGTERM/graceful shutdown handler. Bug #1 (worker-singleton) becomes moot under the new architecture.

  • Hygiene. pw-advisors archived. pw-kincare Dependabot Next.js 16.2.6 PR #2 merged (clears 7-8 of 9 highs). pw-kincare archive deferred until migration into pw-portal-v2 + pw-os-v2 completes.

  • CTO/CISO access grant. [email protected] granted membership in pw-{os,api,api-webhooks,nexus,onchain}-service IAM Postgres roles for diagnostic work. Documented in access-control register; inherited from existing service accounts, no privilege escalation.


2026-05-11

Theme: Option C foundation sequence + PR 8 family (Tier 1 daily market scan). PRs 4 / 5 / 7.5a / 7.5b set the foundation; PR 8 ships the scan code + chat tool; PR 8.2 closes three bugs surfaced by the 2026-05-11 manual trigger + activates the Cloud Scheduler + wires Hurst price history. PR 8.1 (scheduler-only) was rolled into PR 8.2 since the bug-fix work and the activation share the same verification pass. PR 8.2 hotfix (evening) adds force_refresh bypass on the idempotency lock. PR 8.3 (evening) converts the synchronous handler to async queue-and-acknowledge after the 21:10 UTC manual trigger hit the 60s Cloud Run ingress timeout at 59.998s. PR 8.4 (in-flight, scoped tomorrow morning from cron-tick evidence) addresses worker-singleton + MBOUM history + FMP fallback + sources provenance.

Shipped (full 2026-05-11 ledger)

  • PR 8 series — Tier 1 daily market scan foundation (pw-os-v2 #308, pw-shared #34, pw-api #182).
  • PR 8.2 — 3 bug fixes + Cloud Scheduler activation + Hurst historical-closes (4-repo: pw-os-v2 #309, pw-api #183, pw-infrastructure #172, pw-shared #35).
  • PR 8.2 hotfixforce_refresh:true body param to bypass same-day idempotency lock; supersedes prior same-day rows; new market.scan.forced_rerun audit verb (pw-os-v2 #310).
  • PR 8.3 — async queue-and-acknowledge pattern; handler returns 202 in <1s; in-process worker (30s poll, 25-min lifetime, FOR UPDATE SKIP LOCKED claim, orphan recovery for running rows > 10 min old); new GET /api/internal/scheduler/market-scan/:jobId status endpoint (pw-os-v2 #311). Resolves 504 timeout cap.
  • Empirical provider diagnostics — read-only curl probes (MBOUM/FMP/EIA/FRED via Secret Manager) confirming root cause per bug (pw-shared #36).
  • PR 8.4-prep — Bug #2 fixpw-api/src/lib/mboum.ts MboumHistoryBar.date → timestamp; parser prefers timestamp_unix * 1000 integer sort, falls back to Date.parse(timestamp). Six new unit tests cover canonical shape, adjclose preference, ISO fallback, drop-on-no-timestamp, empty body, ticker= param. 632 → 638 pw-api unit tests pass. Shipped to close the universal 68/68 Hurst miss pre-cron so tomorrow's 09:00 UTC tick is a control case for the remaining bugs.
  • PR 8.4-prep — Bug #5 fixpw-os-v2/apps/api/src/lib/scanning/types.ts MacroSnapshot + RawMacroSnapshot both gain optional sources?: Record<string, string>; pass-through in macroSnapshotFromRaw; new data_client.test.ts exercises both branches. 948 → 950 pw-os tests pass. Macro provenance now flows pw-api → GCS scan output.
  • shared/strategy/tier1-layer-overrides.md — authoritative under CTO/CISO authority effective merge date (in PR 8 / pw-shared #34).

In-flight

  • PR 8.4 (narrowed scope after PR 8.4-prep wave closed Bug #2 + Bug #5 tonight):
    • Bug #1 — worker singleton diagnostic. Confirm whether the in-process worker spawns on cron-triggered cold starts (control case at 09:00 UTC tomorrow). If gs://pwllc-pwos-attachments/market-scans/2026-05-12.json exists, bug is manual-trigger-specific.
    • Bug #3 — FMP fallback chain audit. FMP free-tier production key empirically rate-limited on every call (429); pw-api needs to map 429 → provider_throttled distinct from generic miss; also investigate whether stale "miss" cache entries from PR 8 morning's FMP-first regime are still hot (mboum:financials:<symbol> TTL in cache-policy.ts). Architectural decision: paid FMP tier vs. cache-shape fix vs. both.
    • Bug #4 hypothesis empirically rejected — no code action. PR 8.4 release notes document the operator's expectation gap.
    • Bug #2 ✅ closed pre-cron — Hurst should populate tomorrow.
    • Bug #5 ✅ closed pre-cron — macroSnapshot.sources should appear tomorrow.

Empirical provider diagnostics — 2026-05-11 evening (read-only probes, no code changes)

Probes ran against production keys from Secret Manager (pwllc-mboum-api-key, pwllc-fmp-api-key, pwllc-fred-api-key, pwllc-eia-api-key). Findings authoritative for PR 8.4 scoping:

Bug Hypothesis Empirical result Root cause
#1 worker singleton Not initializing in production Deferred to tomorrow's cron-tick control case TBD by 09:15 UTC
#2 MBOUM history 68/68 miss Wrong endpoint or shape MBOUM returns 5 bars HTTP 200 with {timestamp, open, high, low, close, volume} for NVDA/SMH/OKLO pw-api parser reads b.date but MBOUM emits timestamp — silent NaN drop
#3 FMP fallback never fires Path bug or 429 swallow FMP returns HTTP 429 "Limit Reach" on every call; MBOUM CAN fetch ABBV/AMD financials with all expected row labels Two distinct issues: (a) misclassification of MBOUM "misses" (MBOUM has the data); (b) FMP is rate-limited
#4 Macro WTI/CPI wrong Wrong series or scaling EIA RWTC=$109.76 (2026-05-04), CPI YoY=3.286%, PCE YoY=3.496% — scan output matches upstream byte-for-byte No bug; operator's "expected" values are stale
#5 sources field missing Field never shipped pw-api /v1/internal/macro/snapshot emits sources per type; pw-os MacroSnapshot interface lacks the field; deserialization elides Type-erasure boundary on pw-os side

Merged PRs — PR 8.2 (bug fixes + scheduler activation + Hurst wiring)

Bugs caught by the 2026-05-11 PR 8 manual trigger (gs://pwllc-pwos-attachments/market-scans/2026-05-11.json):

# Bug Evidence Fix
1 Silent provider failures (Rule 12 violation) 111/118 tickers scoringProvider:"unavailable" with empty errors[] array scan-bundle returns error_code + providers_attempted envelope; scan_runner pushes an errors[] entry for every unavailable ticker; also for prices_missing when Hurst input is short
2 Wrong provider order (FMP-first failed on ~94% of universe) Only 7/118 scored — mostly US large-caps that FMP free tier covers scan-bundle now tries MBOUM first (paid $49/mo, broad coverage) and falls back to FMP. New fetchMboumFinancials in lib/mboum.ts parses MBOUM v2 financials table format with ×1000 scaling per pw-nexus reference
3 Macro unit + series errors tgaBalanceBillions: 877761, wtiCrudeUsd: 109.76, brentCrudeUsd: 118.26 — TGA was in millions labeled as billions; FRED commodity series returning wrong values WTI/Brent/natgas switched to EIA spot series (RWTC / RBRTE / RNGWHHD via petroleum/pri/spt + natural-gas/pri/fut datasets); TGA switched to Treasury FiscalData v1/accounting/dts/operating_cash_balance (returns millions, divide by 1000 for billions). sources field added to macro/snapshot response so each metric carries its provenance

Features in same PR set:

  • Cloud Scheduler activation (pw-infrastructure terraform/cloud-run-baseline/scheduler-jobs.tf) — new google_cloud_scheduler_job.pwos_market_scan_tier1_daily fires 0 5 * * 1-5 on America/New_York (only PW scheduler job using ET; auto-handles EDT/EST). attempt_deadline=1800s for the 118-ticker walk; retry_count=1 since the agent_jobs idempotency check short-circuits same-day re-runs. Auth pattern matches the four pre-existing pw-os ticks (OIDC + X-Scheduler-Token per the 2026-04-23 newline-trim convention).
  • MBOUM historical-closes endpoint (pw-api /v1/internal/historical-closes/:symbol) — returns up to 250 daily closes via MBOUM /v2/markets/stock/history. pw-os data_client.fetchHistoricalCloses consumes; scan_runner feeds the closes to computeHurst. Recent-IPO tickers with insufficient history surface as prices_missing in errors[] per Rule 12.

Per-repo PR list:

  • pw-api: feat(scan-data): MBOUM-first + EIA macro fix + historical-closes (fetchMboumFinancials, fetchMboumHistory added to lib/mboum.ts; scan-bundle rewritten with provider tracking; macro/snapshot reroutes commodities + TGA; new /historical-closes/:symbol endpoint). 632 unit tests pass.
  • pw-os-v2: fix(market-scan): populate errors[] + Hurst pull + provider tracking (scan_runner.ts pushes Rule 12 entries; apiCallCounts extended with mboum_hits / mboum_misses / fmp_fallback_hits / fmp_fallback_misses / history_hits / history_misses; data_client.fetchHistoricalCloses added). 933 tests pass.
  • pw-infrastructure: feat(scheduler): pwos-market-scan-tier1-daily Cloud Scheduler job (scheduler-jobs.tf new resource).
  • pw-shared: this CHANGELOG entry + CURRENT-STATE.md.

Post-merge verification protocol (per PR 8.2 brief):

  1. Manual trigger via curl -X POST .../scheduler/market-scan with X-Scheduler-Token.
  2. Verify GCS object at gs://pwllc-pwos-attachments/market-scans/2026-05-11.json has:
    • errors[] populated when failures occur (Rule 12 satisfied)
    • apiCallCounts.mboum_hits > fmp_fallback_hits
    • tickerCountScored ≥ 100/118 (90%+ coverage target)
    • macroSnapshot.wtiCrudeUsd in 50-70 range
    • macroSnapshot.tgaBalanceBillions ≤ 1000
  3. If all clean → cron picks up next weekday 5 AM ET automatically.
  4. If issues remain → PR 8.3.

Earlier 2026-05-11 work — PR 8 (Tier 1 daily market scan foundation)

pw-os-v2:

  • PR 8 — feat(market-scan): Tier 1 daily scan foundation. Migration 0050 adds master_tickers (118-row universe with tier / scan_universe / layer_override + EMF scoring snapshot columns) and ticker_fundamentals (full financials cache; F-Score breakdown stored as JSONB; prior-period snapshot fields for delta-based checks). Migration 0051 seeds 118 Tier 1 tickers (74 stocks + 44 ETFs; one-off counting discrepancy vs brief's stated 117 — both included for completeness) with 71 deterministic layer overrides. New lib/scanning/ module: pure-logic scorers (piotroski_scorer.ts 9-check F-Score, croic_calculator.ts 8% threshold, hurst_calculator.ts R/S analysis), classifiers (layer_classifier.ts override precedence, regime_classifier.ts 4-regime + 4-tier with ALIGN_WITH_REGIME/NEUTRAL/FADE_REGIME verdict), thin data_client.ts OIDC wrapper to pw-api (no vendor credentials in pw-os), and scan_runner.ts orchestrator that produces the per-day GCS JSON at gs://pwllc-pwos-attachments/market-scans/YYYY-MM-DD.json. New POST /api/internal/scheduler/market-scan (manual-trigger; X-Scheduler-Token + verifySchedulerToken + agent_jobs insert/transition wired in; idempotent on today's ET date), new GET /api/advisor/scans/market/:date? (advisor-gated read), new get_market_scan chat tool (role: 'advisor', GCS-read with explicit market_scan_not_yet_generated / market_scan_too_old / market_scan_invalid_date reason codes). AgentJobTaskType union extended with 'market_scan'. Chat tool count: 56 → 57. Lambda decay + Perez phase + sector adjustments + ASAN + consistency + adversarial brief are explicitly deferred to PR 9+; the v1 tier ceiling is HIGH_CONFIDENCE when all four checks pass (F-Score ≥ 6, CROIC ≥ 8%, Hurst ≥ 0.55, regime-aligned ≥ 15% layer weight). Tests: 4 new unit suites (piotroski_scorer.test.ts, croic_calculator.test.ts, layer_classifier.test.ts, regime_classifier.test.ts) covering thresholds, override precedence, 4-regime classification, and tier assignment.

pw-api:

  • PR 8 companion — feat(scan-data): /v1/internal/scan-bundle + /v1/internal/macro/snapshot. New router at src/routes/scan-data.ts mounts under /v1/internal/ and exposes (a) per-ticker scan-bundle (FMP profile + 2 annual periods of income/balance/cash-flow, parallel fan-out, fail-soft per upstream) and (b) macro snapshot (11 FRED series fan-out: VIXCLS, T10Y2Y, FEDFUNDS, UNRATE, CPIAUCSL, A191RL1Q225SBEA, PCEPI, DCOILWTICO, DCOILBRENTEU, DHHNGSP, WTREGEN). CPI + PCE YoY computed from 13 monthly observations; yield-curve spread converted from percentage points to basis points. Pre-existing fmp.ts / fred.ts wrappers do the actual upstream work — this PR adds the orchestration only.

pw-shared:

  • New shared/strategy/tier1-layer-overrides.md — authoritative under CTO/CISO authority as of merge date. Documents the 71 deterministic ticker-level layer assignments with per-ticker rationale and precedence rules. Quarterly review cadence.

PR 8 explicit deferrals (PR 8.1 + PR 9+)

  • PR 8.1: Cloud Scheduler Terraform (google_cloud_scheduler_job.market_scan_tier1_daily firing weekday 5 AM ET America/New_York) + scheduler-deployer IAM grants + MBOUM historical-close wrapper to populate closes_daily (currently null → Hurst returns null v1 ceiling). Smoke-test within 1h post-Terraform-apply per the 2026-05-04 silent-401 protocol.
  • PR 9+: Lambda decay (schema field reserved as null), Perez cycle position, sector-specific adjustments (REIT AFFO, BDC NII, MLP DCF), ASAN structural-advantage screen, consistency CV scoring, adversarial brief generation (requires Claude API), base rate anchor, Tier 2 weekly scan.

Discovery-doc drift items closed

# Drift PR
n/a No daily firm-wide scoring pass over the Tier 1 universe; ad-hoc per-ticker chat queries only PR 8 — scan_runner + GCS daily JSON + get_market_scan tool

Earlier 2026-05-11 work — Option C foundation (PRs 1-7, 7.5a, 7.5b)

pw-os-v2:

  • PR #302feat(agent-jobs): foundation + correlation_id propagation. Migration 0047 creates the agent_jobs table (14 columns + 4 indexes + status CHECK enumerating queued | running | succeeded | failed | cancelled) and adds correlation_id UUID columns to both audit_log and ai_audit_log with partial indexes. New lib/agent-jobs/ module exports insertJob, claimNextDueJob (FOR UPDATE SKIP LOCKED), transition (atomic UPDATE + audit_log row in one transaction, state-machine enforced at the application layer), getJob, listJobs, newCorrelationId. The audit-anomaly-scan handler is retrofitted to wrap its existing work in a job row (queued → running → succeeded/failed); the other three Cloud Scheduler ticks stay synchronous until the pattern bakes for a week. Verb AuditAction.AGENT_JOB_TRANSITIONED = 'agent.job.transitioned' added (TypeScript-only; audit_log.action has no DB CHECK constraint). 18 new tests across 7 classes (migration shape, state machine valid + invalid transitions, insert round-trip, lock semantics, retrofit success + failure, correlation_id round-trip). 841 tests pass.
  • PR #303feat(tools): get_client_brief — single-call per-client brief. Fans out via OIDC to existing pw-api /v1/internal/portfolio/client-view + /v1/internal/clients/list and returns a structured JSON brief covering client basic info + Quiltt accounts + onchain holdings + explicit {status:'unavailable', reason:...} for three sections that v1 cannot fill: altruist (no per-client internal endpoint exposed), recent_activity.wealthbox_notes + pending_items (contact_resolution_unavailablewealthbox_contact_id is on the clients row but not in any internal endpoint response keyed by client_id; /portals/lookup-by-email returns it but is keyed by email — wrong direction), open_workstreams (workstream_items_no_client_fk — discovery doc Drift §23.4 confirmed at migration 0042). New assembler at apps/api/src/lib/clients/brief-assembler.ts (no inline assembler existed in pw-os-v2 to extract; the /api/advisor/clients/$clientId/portfolio/view route is a thin OIDC proxy). No route refactor, no new pw-api endpoint. Tool: role:'advisor', category:'portal', no confirmation gate (read-only). Output is JSON-stringified; flows through PR 2's vendor_tool_result PII scan path (locked in by Fixture 6 — newScanState() + scan() against a synthetic brief containing an email substitutes the placeholder correctly). Chat tool count: 55 → 56. 22 new tests across 6 classes (registration, input validation, success, terminal failures, onchain unavailability variants, option flags, partial fan-out tolerance, PII scan readiness). 863 tests pass.

shared:

  • Companion PR to #302 — schema doc shared/strategy/agent-jobs-schema.md (10 sections, ~5,800 words). Schema doc revised after Step 0 to reflect the verb rename (agent_jobs.transitionagent.job.transitioned, caught by the registry's regex-shape test at audit-log.test.ts).
  • Companion PR to pw-os-v2 #303 (get_client_brief). CHANGELOG entry only; no doc churn (the build surfaced a v1 limitation around wealthbox_contact_id exposure that's documented in the PR description rather than a new shared/strategy doc).
  • PR 6 of the Option C foundation sequence — state-doc sync + close #205/#206. New authoritative state doc at shared/PW-STATE-2026-05-11.md capturing the post-PRs-1-through-5 snapshot. Previous PW-STATE-2026-05-09.md moved to shared/archive/state/ (archive/README.md index updated). On GitHub: Protocol-Wealth/pw-os-v2#205 and #206 closed with commenting comments citing merge SHAs — #205 shipped across 07fa9f0 (Phase 1) + 2cba472 (Phase 2) + 8ac3817 (defense-in-depth fix); #206 shipped across a757e56 (rule engine + migration) + 9bafea8 (review surface) + 2235acb (scheduler endpoint) + 036fb64 (PR 4 agent_jobs retrofit enhancement). Cited shared/compliance/wisp-ai-posture-2026-04-30.md from the new state doc's §Compliance posture so it stops being orphaned per discovery doc Drift §23.4 flag.
  • PR 7 of the Option C foundation sequence — firm-wide engineering OS docs. Three new orchestration docs at shared/strategy/: CURRENT-STATE.md (working memory; updated as last write of every PR; <500 words steady-state), BLUEPRINT.md (architectural map; 1,592 words; cited-not-duplicated), ONBOARDING.md (4 paths — employees/founders/advisors, contractor/intern, AI agent, compliance fast path — plus the PR ritual). wisp-ai-posture-2026-04-30.md cited from BLUEPRINT.md §5 (AI surface) for its permanent home. Per-repo CLAUDE.md ritual updates land in companion PRs on pw-os-v2 / pw-api / pw-portal-v2 / pw-infrastructure / pw-website (each adds "update CURRENT-STATE.md as last write of every PR" to the post-merge deliverables list). Closes the Option C foundation sequence structurally; PR 8 advisor-agent-v1 is the next session.
  • PR 7.5a — fix(agent-jobs): replace UPDATE-after-INSERT on audit_log with widened write_audit_log signature (pw-os-v2 #305). Closes the PR 4 dead-code-in-prod bug surfaced 2026-05-11 by manual scheduler-trigger diagnostic: the agent_jobs runner's transition() did UPDATE audit_log SET correlation_id = … AFTER the SECURITY DEFINER INSERT, tripping the audit_log_no_update BEFORE-UPDATE trigger (migration 0015) on every transition. Every audit-anomaly-scan tick from PR 4 merge (10:53 UTC) through 15:23 UTC failed silently with "audit_log rows are immutable; UPDATE not permitted" and returned HTTP 200 to Cloud Scheduler, defeating the retry policy. Migration 0048 widened write_audit_log() with p_correlation_id UUID DEFAULT NULL (10th) + p_trace_id TEXT DEFAULT NULL (11th) — bundled to also close the latent telemetry-middleware trace_id gap from the original migration 0015 header (where trace_id was described as UPDATE-after-INSERT but no middleware ever existed; trace_id has been NULL on every audit_log row since the table shipped, and the planned middleware would have hit the same trigger). Handler now returns HTTP 500 on agent_jobs.setup_failed so Cloud Scheduler retries. CI gap closed: production triggers were covered only by live-DB integration tests (DATABASE_URL-gated); CI default lane ran without a DB, so PR 4 shipped past lint + unit-test. The mock pg fake in lib/agent-jobs/index.test.ts now mirrors audit_log_no_update + audit_log_no_delete triggers — any future UPDATE-on-audit_log defect fails at unit-test time. 867 tests pass; lint clean; typecheck clean. Post-deploy verification 2026-05-11 15:48:26 UTC: manual gcloud scheduler jobs run pwos-audit-anomaly-scan → Cloud Scheduler logged HTTP 200, pw-os emitted INFO audit_anomaly_scan.complete with job_id 5e2254a0-…, correlation_id e3bba48e-…, duration_ms 517; zero ERROR logs in the trigger window. The retrofit is live in production for the first time since merge.
  • PR 7.5b (this PR) — feat(session+cost): sliding session window + per-message cost visibility + Opus pricing correction (pw-os-v2 branch feat/pr75b-sliding-session-and-cost-visibility). Two bundled concerns:
    • Sliding session window. Advisor was being kicked out mid-task by the 15-minute idle ceiling because the cookie didn't refresh on activity. SessionClaims gains an initial_sign_in_at claim preserved across re-issues; reissueSessionToken(claims) re-mints the cookie with a fresh 15-minute TTL while anchoring an absolute 4-hour cap from initial sign-in (Reg S-P §248.30(b) "reasonable" session length + stolen-cookie blast-radius reduction); loadSession in role-guards.ts re-issues on every authenticated request and treats isAbsoluteMaxExceeded as a soft 401 (caller sees unauthenticated → SessionIndicator renders the force-expiry modal). New GET /api/session/status endpoint (mounted in auth.ts behind requireSession) returns {expires_at, absolute_max_at, seconds_until_idle_logout, seconds_until_absolute_max, initial_sign_in_at} — single source of truth for the frontend countdown; the act of polling this endpoint itself slides the session, which is intentional (an advisor reading a long Claude response shouldn't time out under their nose).
    • Per-message cost visibility. Migration 0049 adds cache_creation_input_tokens INTEGER + cache_read_input_tokens INTEGER to both messages and ai_audit_log (nullable; existing rows stay NULL — forward-only). claude-client.ts already reads these counters off Anthropic's message_start.usage shape; appendMessage + writeAiAudit (both in conversations.ts) now persist them. SSE done event from routes/chat.ts carries cache_creation_tokens / cache_read_tokens / cost_usd; GET messages computes cost_usd server-side per assistant row via estimateCost. Frontend: Message type gains cost_usd?: number | null; MessageBubble.tsx replaces the left-aligned {in} in · {out} out footer with a right-aligned ~$0.04 cost (4-decimal precision under $0.10 so cents-of-cents moves on cached turns are visible); new <SessionIndicator> chip + cumulative $0.34 · 18 turns count land in the chat header.
    • ⚠️ Opus pricing correction. claude-cost.ts had Opus at $15/MTok input + $75/MTok output (the historical Claude 3 Opus rates). Anthropic's published rate for Opus 4.x is $5/$25 — 3× lower across the board. The correction is forward-only; no historical backfill. Every Opus cost figure in Langfuse generations, ai_audit_log.detail, and any internal spend report computed BEFORE 2026-05-11 is overstated by 3×. A drift-fence test (Opus pricing anchor — 2026-05-11 correction in claude-cost.test.ts) now anchors the rates so any future regression fails CI loudly instead of being baked into another month of spend data. Sonnet ($3/$15) and Haiku ($1/$5) rates were already correct.
    • Cache rates. CACHE_READ_RATE_MULTIPLIER = 0.1 (cached reads bill at 10% of normal input rate; this is the prompt-caching discount) and CACHE_CREATION_RATE_MULTIPLIER = 1.25 (cache creation bills at 125%, a one-time premium). Without these, the ~25K-83K cache_read_tokens/turn we observe post-PR #242 would have inflated displayed cost by 5–10× on cached turns and made the per-message footer useless for sanity-checking that caching was working.
    • Tests: 894 pass (was 867 in PR 7.5a). 40 new tests: 15 in claude-cost.test.ts covering base pricing, cache-rate multipliers, realistic-turn end-to-end, format helpers, and the Opus drift fence; 25 added to session.test.ts covering initial_sign_in_at (fresh + re-issue paths), reissueSessionToken (expiresAt + absoluteMaxAt anchoring), isAbsoluteMaxExceeded (legacy cookie, fresh, expired, boundary). Lint clean; typecheck clean across both apps/api and apps/web.
    • CHANGELOG flag for future spend analysts: when reconciling Opus spend across the 2026-05-11 boundary, divide all pre-correction figures by 3 to convert to the actual billed amount.

Discovery-doc drift items closed

# Drift PR
9 CLAUDE.md §Database listed agent_jobs + scheduled_jobs tables that no migration created pw-os-v2 #302
Finding 3 Scheduler endpoints did all work synchronously with no run-id / idempotency / first-class examiner-pivotable record pw-os-v2 #302 (audit-anomaly-scan retrofit; other 3 ticks pending post-bake PR)
Finding 1 Chat surface had no aggregator that fanned Wealthbox + Quiltt + Altruist + onchain into one tool result for an advisor agent pw-os-v2 #303 (with three documented v1 limitations: altruist per-client endpoint not exposed, wealthbox contact_resolution_unavailable, workstream_items_no_client_fk)
12 Issues #205 + #206 listed open in PW-STATE-2026-05-09 but the underlying work shipped pw-shared PR 6 — issues closed with merge-SHA citations; new authoritative PW-STATE-2026-05-11.md
n/a No curated reading path for humans + AI agents picking up cold pw-shared PR 7 (this PR) + per-repo CLAUDE.md ritual updates — CURRENT-STATE.md + BLUEPRINT.md + ONBOARDING.md orchestrate the existing 30+ docs

Compliance posture

  • SEC Rule 17a-4 (books-and-records, 7y retention): every agent_jobs state transition writes one immutable audit_log row. The agent_jobs row itself mutates by design; the transition history is fully reconstructable from audit_log rows of action='agent.job.transitioned' ordered by created_at. The immutability trigger on audit_log from migration 0015 stays enforced; the new correlation_id column is ADD-COLUMN only.
  • Audit verb shape: agent.job.transitioned matches the registry's 3-segment dotted-lowercase-with-no-underscore-in-first-segment convention (auth.session.created, email.form.submitted, gsheet.rows_appended pattern). Enforced by audit-log.test.ts > AuditAction registry.
  • No DB CHECK constraint on audit_log.action (verified at migrations/0015_audit_log.sql:95action TEXT NOT NULL), so the new verb is a TypeScript-only addition. PR 1's drift-fence test targets ai_audit_log.action only and is unaffected.

Sequence revision

The Option C closing PR shifted: PR 5 → PR 6 (state-doc sync, queued) → PR 7 (engineering-OS orchestration docs — CURRENT-STATE.md + BLUEPRINT.md + ONBOARDING.md) → PR 8 (advisor agent v1). PR 7 was originally the advisor-agent buildout; the engineering-OS docs that orchestrate the existing 30+ docs into curated reading paths now sit in front of the agent work to give it a stable orientation surface.

Known follow-ups for PR 8 (preconditions)

Deferred per PR 4 + PR 5 briefs; documented in pw-os-v2/CLAUDE.md + the relevant strategy / assembler docs:

  • Stale-job sweeper for agent_jobs rows that exceed Cloud Run timeout (60-minute hard cap). Non-blocking for the 5-second audit-anomaly-scan; may matter for PR 8's longer-running market_scan task.
  • correlationId through ToolExecutionContext so chat tools invoked under an agent_jobs context propagate the contract. Out of scope for PR 4 (audit-anomaly-scan doesn't call tools).
  • wealthbox_contact_id exposure on /v1/internal/clients/list or /v1/internal/clients/:id — closes the v1 contact_resolution_unavailable gap in get_client_brief, unlocks per-client Wealthbox notes + tasks. Probably a small pw-api PR landing alongside or before PR 8.
  • Altruist per-client read endpoint — closes the v1 altruist_per_client_endpoint_not_exposed gap if Altruist becomes part of PR 8's advisor agent surface.
  • Per-client linkage on workstream_items — separate evaluation against the existing firm-internal workstream model.

Discovery-doc cross-reference

shared/strategy/pwos-advisor-agent-discovery-pass-2.md Drift #9, Finding 3, Finding 1, Drift §23.4 (workstream_items_no_client_fk). shared/strategy/agent-jobs-schema.md (new in PR 4) §2 (schema), §3 (state machine), §4 (audit posture), §5 (correlation_id contract), §6 (worker pattern), §7 (retrofit), §10 (open questions + deferred items). Sequence (revised): PRs 1 + 2 + 3 + 4 + 5 of 8 in the Option C foundation; PR 6 (state-doc sync + close #205/#206) is queued, then PR 7 (engineering-OS docs), then PR 8 (advisor agent v1).


2026-05-10

Theme: Option C foundation sequence — PR 1 (audit enum + workspace header), PR 2 (PII manifest cross-turn + within-turn), and PR 3 (Langfuse coverage on 4 non-chat surfaces) closing correctness + observability gaps surfaced by the 2026-05-09 advisor-agent discovery pass. Foundation work for the PWOS Advisor Agent v1; agent runner code is PR 7 in a later session.

Merged PRs

pw-os-v2:

  • PR #298feat(audit): widen ai_audit_log.action enum + standardize workspace-id header. Migration 0046 admits conversation.mode_changed (silently dropped on insert since 0038/session_mode landed; chat.ai_audit_write_failed events with err=23514 were the symptom). Two missing defaultHeaders['X-PW-Workspace-Id'] sites fixed (compliance/analyze.ts, workstreams/summarizer.ts); inbound-ai-classifier.ts was flagged unverified in the discovery doc and verified clean (routes through claude-client.getClient(), inherits the canonical header). Two regression-prevention drift fences added in apps/api/src/lib/. 811 tests pass; lint clean; typecheck clean.
  • PR #299feat(pii): merge tool-result manifest cross-turn + within-turn. Tool-result-discovered PII (fresh emails, crypto addresses, JWTs the advisor never typed) was detected by the in-process piiScanState but never reached the per-conversation Redis manifest. Two bugs sharing one fix point in claude-client.ts's tool-result handling block: cross-turn invariant break (turn N+1's <EMAIL_X> reached Anthropic as literal placeholder string) + within-turn leak (same-turn assistant response containing <EMAIL_X> streamed to the SSE writer with the literal placeholder visible to the advisor). Fix wires mergeAndSaveManifest after the existing tool-result scan, mutates rehydrateManifest.placeholders in place so the rehydrator's closure sees the new entries, drops the empty-placeholders short-circuit in stream-rehydrator.ts so empty-but-non-null manifests select the buffer-and-rehydrate variant, and seeds an empty manifest when req.conversationId is set. New manifestSyncQueue serializes parallel tool merges to prevent Redis SET race. Structured log: pii.manifest.tool_merge. 6 test fixtures (cross-turn, within-turn, collision, NAME/ORG/EVENT regression, TTL parity, parallel tools). 817 tests pass.
  • PR #301feat(observability): Langfuse coverage on 4 non-chat surfaces. Workstream summarizer, compliance analyzer, social drafts, and inbound classifier now emit Langfuse traces. Pattern documented in-tree at pw-os-v2/apps/api/src/lib/langfuse/PATTERN.md (drafted as Step 0 of the PR before any wiring). observability.ts extended with generic startTrace / recordGeneration / finalizeTrace + classifyFailure (renamed from classifyChatFailure; old name kept as deprecated alias — removal tracked in Protocol-Wealth/pw-os-v2#300). CI guard lint-langfuse-fire-and-forget.mjs WRAPPER_ALLOWED_CALLERS expanded 1 → 5 surfaces, with a comment pointing to the runbook section governing the set. Trace names: pwos.chat.message, pwos.workstream.summarize, pwos.compliance.analyze, pwos.social.draft, pwos.inbound.classify. 6 test fixtures (4 per-surface + 1 sensitive-data with compliance/analyze emphasis + 1 failure-mode regression). 823 tests pass. Sensitive-data discipline locked: vendor PDF text reaches Anthropic under the file-level skip-pii-scan carve-out but does NOT reach Langfuse trace metadata; the regression test serializes all captured trace args and asserts the PDF content is absent. Optional smoke-test script at pw-os-v2/apps/api/scripts/langfuse-smoke-test.ts.

shared:

  • This PR — CHANGELOG entry; companion PR 2 schema doc shared/strategy/pii-manifest-schema.md (drafted in PR-2 development cycle, included here).
  • Companion PR 3 runbook update at shared/runbooks/langfuse-self-host-deploy-plan.md adding a new §Coverage section that enumerates the 5 allowlisted Langfuse wrapper callers (chat + 4 new) and the governance rules for adding a sixth. Lockstep-required with WRAPPER_ALLOWED_CALLERS in the lint script.

Discovery-doc drift items closed

# Drift PR
4 ai_audit_log.action CHECK diverged from runtime callers pw-os-v2 #298
10 2/4 direct new Anthropic() sites lacked X-PW-Workspace-Id header pw-os-v2 #298
Finding 2 (cross-turn) Tool-result PII never persisted to Redis manifest; turn N+1 lost cross-turn placeholder consistency pw-os-v2 #299
Finding 2 (within-turn — surfaced during PR 2 reconciliation) Same-turn assistant response leaked literal <EMAIL_X> to SSE writer for fresh tool-result-discovered entities; rehydrator captured manifest pre-tool-loop with no growth path pw-os-v2 #299
11 4 non-chat Anthropic surfaces (workstream summarizer, compliance analyze, social drafts, inbound classifier) bypassed Langfuse trace coverage pw-os-v2 #301

Compliance posture

  • SEC Rule 17a-4 (books-and-records): conversation.mode_changed inserts no longer dropped at the CHECK boundary. Existing rows untouched. ai_audit_log has no immutability trigger by design (migration 0004 commentary "rows are append-only by application contract"); migration 0046 only DROP-and-readds the named CHECK constraint.
  • AI governance: forensic attribution restored on the four direct Anthropic SDK construction sites in apps/api/src/lib/. ZDR enrolment is bound to the API key (workspace-scoped at the account level), so the missing header was an audit-affordance gap rather than a ZDR breach.

Discovery-doc cross-reference

shared/strategy/pwos-advisor-agent-discovery-pass-2.md §5 (audit_log + ai_audit_log), §16 (Anthropic API client configuration), Drift #4, Drift #10, Finding 2. shared/strategy/pii-manifest-schema.md (new in PR 2) §6 (Write path — tool-result entities), §7 (Read path — verified verdict + reconciled trace), §7.1 (Within-turn rehydration fix point). pw-os-v2/apps/api/src/lib/langfuse/PATTERN.md (new in PR 3) — canonical Langfuse wiring pattern; future Anthropic call sites must consult it before adding to the wrapper allowlist. shared/runbooks/langfuse-self-host-deploy-plan.md §Coverage (new in PR 3) — source of truth for the wrapper allowlist; lockstep with WRAPPER_ALLOWED_CALLERS in the lint script. Sequence: PRs 1 + 2 + 3 of 6 in the Option C foundation; PR 4 (agent_jobs foundation + correlation_id propagation) is queued.

Post-merge verification (recorded for the SEC books-and-records trail)

Cloud Logging query against the pw-os service:

resource.type="cloud_run_revision"
resource.labels.service_name="pw-os"
jsonPayload.action="chat.ai_audit_write_failed"
jsonPayload.err=~"23514"

Pre-merge (last 7 days) expected non-zero; post-merge (first 24h after revision rollout + migrate.applied for 0046_ai_audit_log_action_extension.sql) expected zero.


2026-05-09

Theme: 10-PR shipping day across 5 active repos. Production bug-fix wave (Wealthbox / FMP / FRED / onchain), full GCP+repo+vendor infra audit, ~$148/mo cost optimization landed (Cloud SQL HA drop), placeholder Cloud Run services retired, pw-portal-v2 CI gates brought to parity with pw-os-v2, vendor doc gaps closed (7 new + fmp.md refresh), shared/ archive cleanup (6 stale state snapshots + 4 closed planning docs moved to archive/).

Merged PRs

pw-api:

  • PR #177 — fix(wealthbox): accept drifted GET response shapes (linked_to array + meta-only envelope tolerance).
  • PR #178 — feat(onchain): stub /v1/internal/onchain/* with kind=onchain_migration_in_progress 503; pw-onchain (Fly) decommissioned.
  • PR #179 — fix(api): FMP path drift (etf-holder rename + insider-trading v3 base) + vendor key trim across all FMP/MBOUM/Tradier/FRED/etc keys (defends against paste-CR rotation artifacts).
  • Issue #180 filed: ETF sector/country weightings still 404 — needs live-key probe.

pw-os-v2:

  • PR #297 — fix(chat): render clean migration message for onchain tools when pw-api returns the migration stub.

pw-portal-v2:

  • PR #47 — chore(ci): wire ESLint flat config + Contract #3 compliance gates (mirror of pw-os-v2 #289). Closes silent-broken lint script (eslint dep was never installed). 3 lint errors fixed at source: D18 hardcoded-haiku model literal → env-driven; useless-assignment on tool-use input parse; unused c arg on /auth/register.

pw-infrastructure:

  • PR #169 — chore(c5): remove pw-nexus / pw-onchain / pw-strategies Cloud Run placeholder services (-322 LOC; 14 cross-service IAM bindings → 3). Follow-up PRs queued for secrets/SA/DB cleanup per cross-workspace race rule.
  • PR #170 — chore(c3): drop Cloud SQL HA (REGIONAL → ZONAL). $148/mo savings ($1,776/yr). Investigation found 0 failover events in 21d of REGIONAL operation; pre-client status; SEC compliance unaffected; single-flag reversible. Apply outside business hours (3-5min downtime expected).

shared:

  • PR #23 — docs(api): 7 new vendor docs (brave, fred, eia, edgar, etherscan, subgraph, mercury) + fmp.md refresh covering today's path corrections + env-trim defense.
  • This PR — archive/ cleanup; new authoritative PW-STATE-2026-05-09.md; CHANGELOG update.

Operational state changes

  • pwdashboard.com fully retired: Cloudflare Worker confirmed deleted from account; repo archived on GitHub; DNS 301s to protocolwealthllc.com. Altruist redirect URI rotation pending Altruist support response (no production traffic gated on this).
  • Spend baseline captured: $526/mo total GCP last 30d (Cloud SQL $297 + Memorystore $134 + Cloud Run $88). Trending to ~$378/mo after PR #170 applies.
  • Active GCP runtime collapsed: 8 Cloud Run services → 5 active (pw-api, pw-os, pw-portal, pw-api-webhooks, langfuse). Removed: pw-nexus, pw-onchain, pw-strategies (all cloudrun/hello placeholders).

Decisions logged

  • C1 Memorystore tier change: declined; keep STANDARD_HA 5GB.
  • C3 Cloud SQL ZONAL: approved + shipped (PR #170).
  • C4 Cloud SQL right-size: held; revisit after 7-day Cloud Monitoring snapshot of post-ZONAL CPU/RAM.
  • C5 Cloud Run cleanup: approved + foundation shipped (PR #169).
  • S3 FRED key clean rotation: declined; code-side .trim() accepted as the durable fix.
  • Custody terminology pinned: Turnkey for client wallet custody; Fordefi MPC + Coincover for firm treasury; Fortary evaluation-only.

Issues filed

  • pw-api #180 — FMP /stable/etf-sector-weightings + /stable/etf-country-weightings still 404; needs live-key probe to determine path drift vs tier gating.

Memory updates

  • project_phase_x_status_2026_05_03.md — pw-onchain decommissioned. Architecture is GCP-only (no Fly/Upstash/Neon).
  • project_advisor_portfolio_mvp_2026_04_29.md — onchain section noted as empty/unavailable until pw-api absorbs the wallets+snapshots flow.
  • feedback_console_to_structured_log.md — apps/api uses logInfo/logWarn/logError from lib/structured-log.ts; ESLint enforces no-console=error.

Shared docs cleanup

  • New: PW-STATE-2026-05-09.md (authoritative, supersedes 4 prior dated state docs).
  • New: 7 vendor reference docs in api/.
  • Refreshed: api/fmp.md (was a 2026-03-22 stub referencing pw-nexus; now reflects pw-api reality).
  • Archived to archive/state/: PW-STATE-2026-05-01/-02/-04, PW-STATUS-2026-05-01-EOD, MORNING-REPORT-2026-05-01, altruist-sync-audit-2026-05-01.
  • Archived to archive/planning/: PW-NEXT-SCOPING-2026-05-01 (decisions executed), PW-PHASE-B-REVIEW-2026-04-25 (phase closed), ZDR-UPDATE-REVIEW-GUIDE (one-shot), PW-SUBPROCESSORS-v1_2-DRAFT (never published).
  • Archived to archive/chat-audit/: pre-EOD chat-tools-audit-deltas-2026-05-02 (EOD version is superset; promoted to root).
  • New: archive/README.md (index of what's in archive + when to consult).

2026-04-26

Theme: Tuesday signup link delivery for CCO (also serves as poster); cost-attribution labels rollout; cookie cutover Phase 1 JWKS diagnostic; canonical AGENTS.md landed in shared; session-end-hygiene skill registered.

Merged PRs

pw-os-v2 (Tuesday signup link build):

  • PR #65audit_log foundation (mirror of pw-api migration 0015) for forms/intake instrumentation.
  • PR #66 — forms data migration (legacy form rows → canonical schema).
  • PR #68 — hosted form routes (/f/<slug>) under advisor-public surface; CSP + secureHeaders preserved.
  • PR #69 — form seed for webinar-newsletter-signup (the Tuesday link).
  • PR #71 — Inter font + form polish CSS. Discovered as no-op at runtime: secureHeaders middleware override stripped inline <style> blocks. PR shipped CSS but browsers received style-src 'self'. Followed by #72.
  • PR #72 — external /assets/form.css (actual rendering fix). Option-3 over (1) skipping secureHeaders (would strip 12 protective headers) and (2) hash-allowlist (brittle).
  • PR #70 — closed as side-effect of #72.

pw-os-v2 (cookie cutover Phase 1):

  • PR #59 — dual-issuer JWKS verification (RS256 via pw-api JWKS + HS256 fallback). Code-wired-and-deployed; inert until PW_API_JWKS_URL set on pw-os Cloud Run (pw-infrastructure #91). HS256 fallback handles all session verification today.

shared:

  • PR #11 — canonical AGENTS.md + llms.txt landed (mid-session merge during state-hygiene pass — scope adjustment for P2/P5/P7).
  • PR #12 — caching layer architecture design doc (CCO sign-off request filed; awaiting CCO — 6-point response).
  • PR #13session-end-hygiene skill + dependabot-majors skill registration; skills/README.md self-contained registry created (Option C — no pw-website/AGENTS.md v-bump conflict per skill anti-pattern #3).

Issues filed

  • pw-infrastructure#91 — finish step for cookie cutover Phase 1: set PW_API_JWKS_URL on pw-os Cloud Run via terraform. Producer endpoint already public at pw-api-webhooks URL with valid RS256 public key.
  • pw-infrastructure#90 — cost-attribution labels into terraform (currently applied via additive gcloud --update-labels; needs codification).
  • pw-os-v2#73 — iframe-embed fix (post-Tuesday cleanup).
  • pw-os-v2#74Cache-Control header on /assets/* (post-Tuesday cleanup).
  • Internal #94 — Drizzle drift in pw-os-v2/CLAUDE.md. Closed via this hygiene pass (raw SQL migrations under apps/api/migrations/; no Drizzle).
  • Internal #98logInfo on every audit_log write (ops visibility; post-Tuesday cleanup).
  • Internal #100AGENTS.md producer-consumer verification pattern formalization (post-Tuesday cleanup).
  • (Plus held-batch items #88, #89 on pw-api half of Bundle B; gates on CCO Tuesday confirmation.)

Decisions

  • Tuesday signup link gate verification (gate 17): CCO approved code-review + observed-behavior verification position. Code-review-plus-observed-behavior accepted as defensible verification standard.
  • Cookie cutover Phase 1 framing corrected: doc claim "consumed in production" → "code-wired-and-deployed; configuration pending." Three separate verifications now required: (a) code merged, (b) deployed to revision, (c) env var wired. Pattern formalized as producer-consumer verification protocol in protocols/SESSION-PROTOCOLS-2026-04-26.md §7.
  • Three-strikes rule formalized after polling-fix saga (3 broken attempts at canary Ready=True polling): stop and reassess after 3 failed attempts at the same problem. Final fix: jq-based revision query (PR #69 of pw-api-side polling work, Task #95 closed). Evidence file §1.
  • Single-agent-per-repo rule formalized after M-tier stash collision. Evidence file §2.
  • Status-doc accuracy pattern: counts must be verified by direct grep of source code, not derived from prior docs. Chat-tool count corrected from "14" → 13 by counting registerTool({ calls in apps/api/src/lib/tools/. Evidence §8.
  • Cost-attribution label schema locked: app/env/cost_center applied to 9 production resources (7 Cloud Run services + Cloud SQL primary + Memorystore Redis) via additive gcloud --update-labels. BigQuery billing export enabled on pwllc-prod. Evidence §9.
  • Cache-as-not-books-and-records framing locked: Memorystore Redis is cache layer, SEC 17a-4 retention does NOT bind cache. Audit_log + Cloud SQL remain authoritative books-and-records. Awaiting CCO sign-off on caching architecture (6-point request open). Evidence §10.
  • Memorystore Redis stays in architecture: confirmed needed for caching layer Phase 1 + future features.
  • Drizzle drift correction: pw-os-v2 uses raw SQL migrations under apps/api/migrations/ — no Drizzle, no schema.ts, no ORM. Direct pg.Pool. Internal task #94 closed.

Artifacts created

  • shared/protocols/SESSION-PROTOCOLS-2026-04-26.md — evidence record for ~10 protocols this session (cross-references AGENTS.md for the 6 already-canonical; full evidence for the new patterns).
  • shared/CHANGELOG.md — this file.
  • shared/compliance/cco-approvals/PW-CACHING-LAYER-CCO-REQUEST-2026-04-26.md — caching architecture CCO request artifact (awaiting CCO).
  • shared/status/PW-STATUS-AND-CAPABILITIES-2026-04-26.md (P6, in progress) — evening-doc successor compiling today's work.
  • pw-website/AGENTS.md v7.5.0 DRAFT (P7, in progress) — engineering-standards additions; not distributed to other PW repos this session.

Corrections applied (state-hygiene pass)

  • shared/status/PW-STATUS-AND-CAPABILITIES-2026-04-25-EVENING.md: surgical edits across TL;DR, §0 verdict, §1 cumulative claim, §4 D3 row, §4 Phase 1 bullet, §6 JWT canonical issuer table row, §6 lead sentence, "Verified-and-corrected" entry. Pattern: replace "Phase 1 shipped" / "consumed in production" with "code-wired-and-deployed; configuration pending; HS256 fallback handling all session verification." Reference: pw-infrastructure#91.
  • pw-os-v2/CLAUDE.md: 4 Drizzle references replaced (raw SQL migrations under apps/api/migrations/; no schema.ts; no ORM; pg.Pool directly); chat-tool count "14" → 13 with provenance.
  • pw-os-v2/README.md: chat-tool count "14" → 13 with provenance.
  • Memory anchor project_chat_tools_wave_2026_04_23.md: "11 new chat tools" / "14 total" → 13 chat tools registered, verified 2026-04-26 against registerTool({ calls. Catalog updated.

Cloud Run / infra

  • 9 production resources cost-labeled (7 Cloud Run services + Cloud SQL primary + Memorystore Redis).
  • BigQuery billing export enabled on pwllc-prod.
  • pw-os Cloud Run revision serving PR #59-merged consumer code (RS256+HS256 verifier deployed; inert per env-var gate above).

Held (preserved per §6 surface protocol; not started this session)

  • Bundle B pw-api half (#88, #89) — gates on CCO confirming Tuesday posts.
  • Email pipeline v1.0 full scope — same gate.
  • pw-os-v2#73 (iframe-embed fix) — post-Tuesday.
  • pw-os-v2#74 (Cache-Control on /assets/*) — post-Tuesday.
  • INET unification (#67) — post-Tuesday.
  • pw-infrastructure#90 (labels into terraform) — post-Tuesday.
  • pw-infrastructure#91 (PW_API_JWKS_URL env via terraform) — post-Tuesday.
  • Internal #98 (logInfo on every audit_log write) — post-Tuesday.
  • Internal #100 (AGENTS.md producer-consumer verification pattern) — post-Tuesday.
  • Caching layer Phase 1 build — gates on CCO §6 sign-off.
  • AGENTS.md v7.5.0 distribution to all PW repos — draft only at pw-website canonical; full distribution deferred to separate session.
  • pw-infrastructure/CLAUDE.md drift, 84 stale local branches, 2 Dependabot alerts — deferred.

Older entries

This is the inaugural cross-repo CHANGELOG. Pre-2026-04-26 history is in:

  • Per-repo CHANGELOG.md files where present.
  • archive/status/CHANGELOG.md for status-doc revisions (was status/CHANGELOG.md pre-2026-05-14 reorg).
  • git log across the active PW estate.
  • Memory anchors under ~/.claude/projects/-home-nick-projects-pw/memory/.

Historical archives

Per the post-2026-05-14 reorg policy, this file carries the current quarter only. As entries age past the current quarter, they split per-month into archive/changelog/YYYY-MM.md. The split happens at quarter boundaries — at the first commit of the next quarter, the prior quarter's entries move out in a single PR.

Current state: Q2 2026 (April + May + June) in progress; nothing split out yet because the cross-repo CHANGELOG started 2026-04-26. The split convention is documented at archive/changelog/README.md.

What this page is. A transparency surface describing Protocol Wealth's engineering work — what merged, when, and at a high level why. It is aggregate substrate material; it is not a description of investment advisory services, not a performance record, and not advice.

What this page is not. Not investment advice. Not advisory performance. Not a record of client accounts, positions, or returns. Public-repo pull-request references link to GitHub; internal-repo references are rendered as text rather than links. (891 internal-refs on this build.)

Protocol Wealth, LLC is an SEC-registered investment adviser (CRD #335298). Registration with the SEC does not imply a certain level of skill or training. Full regulatory disclosures are linked from the site footer.