PR #17 |
FINAL PR of Wave 10 — Metrics + Library + Audit viewer + Settings + Last-seen-by badge + Peer-spawn preview (L-complexity, multi-router substrate+UI delivery). Substrate: NEW app.sp_user_pref_save (SECURITY DEFINER, last-write-wins UPSERT on (upn,key), ADR-W10-9 §3.1 carve-out from HS-4L; #variable_conflict use_column directive resolves OUT-TABLE/table-column ambiguity in ON CONFLICT — P5 retrospective lesson) over the pre-existing app.user_prefs(upn,key,value,updated_at) table. 7 NEW haleon.v_metrics_* views (decisions_weekly, risks_weekly, use_case_transitions, costs, recognition_weekly, audit_velocity_daily, reuse_funnel) + 7 NEW portfolio.v_metrics_* alias views (single-engagement MVP; UNION ALL substrate-ready). Reuses pre-existing haleon.sp_record_read + haleon.sp_verify_chain. Corrective haleon.v_human_touchpoints WHERE narrowing per ADR-W10-9 §3.3: switched from fail-OPEN BLACKLIST to variant-A fail-CLOSED pattern (ar.class='turn' AND ar.triggered_by LIKE 'ui.%' AND triggered_by <> ALL (chrome-blacklist)) — closed pre-existing live leak of 56 read.row + 30 ddl.*/deploy.* rows that had been surfacing in the touchpoint feed. View re-CREATE preserved 12 cols + ORDER BY + all 10 CASE branches verbatim (column-order trap avoided per PR #16 lesson). All grants applied for w10_app. SA-ADR-W10-9 ratified (adr_number 18, DAB id fe1f5650-412b-493a-a62a-69749e5eb1e7): app.* schema scope as operator-personal cross-engagement chrome; user_upn scoping convention; m_record_row_read semantic = class='turn'+triggered_by='read.row' (W10-4 spine CHECK widen REJECTED — number stays RESERVED); GDPR posture for reader-tracking rows under engagement DPA; deliberate exclusion of app.* writes + reader-observation rows from v_human_touchpoints. tRPC: 5 NEW routers (metrics, library, audit, settings, context) wired into root.ts; namespaces read.metrics.{engagement,portfolio} + read.library.list + read.audit.{list,chainIntegrity,lastSeenBy} + read.settings.get + mut.settings.savePref + read.context.peerSpawnPreview; __setExecutor test-seam pattern; protectedProcedure; audit.lastSeenBy invokes sp_record_read as side-effect per ADR-W10-9 §3.2 (fire-and-log-on-fail; viewerRole = ctx.user.role ?? 'human:'+upn.split('@')[0]); NO row_hash/prev_hash leak to client. 5 NEW SPA routes: /metrics portfolio ECharts grid + /e/$engagement/metrics per-engagement grid + /e/$engagement/library (over existing portfolio.pattern_library+haleon.reuse_*) + /e/$engagement/audit (virtualised agent_runs table + chain-integrity badge via sp_verify_chain) + /e/$engagement/context/peer-spawn-preview (7-layer forensic read, per-request no cache, graceful FS degradation). <LastSeenBadge> component injected cross-cutting on 4 detail pages (decisions, risks, use-cases, ADRs — recognitions has no standalone detail route, drawer-only). ECharts via echarts-for-react + chartTheme.ts single-source palette with --cp-chart-1..8 tokens (defined in iter-2 Δ-A in apps/spa/src/index.css light + dark blocks; regression test pinned). 6 single-source helpers (settingsKeys, chartTheme, auditFilters, relativeTime, metricsPanels, settingsErrors). TanStack day-1 contract honoured (0 useEffect in new routes; trpcClient only in queryFn/mutationFn); drift-bombs clean (0 var(--color-*) in new files; 4 LastSeenBadge sites). Tests: API 234/234 + SPA 106/106 (was 233+105 post-iter-1, +1 lastSeenBy side-effect case + 1 chartTheme regression case in iter-2). Typecheck exit 0. 4 functions unchanged (roles + trpc + skillsCommunicationsRecordOutbound + timerRefreshSearch) — peer-spawn-preview rides trpc catch-all. Post-deploy substrate smoke: chain OK 738→739 rows (ship audit row appended), sp_user_pref_save upsert round-trip GREEN (post-hotfix), 14 v_metrics_* views populated, v_human_touchpoints read.row leak = 0. Peer ring: CoS READY-TO-SHIP, SA (fresh cold-spawn after rotation, ratified ADR-W10-9 + variant-A filter) co-signed READY-TO-SHIP, Steward substrate + source attested. Carry-forwards (W11+): Cnew-app-rls (RLS on app.user_prefs); Cnew-metrics-materialize (MV + Functions-timer refresh if any panel measures >50ms); Cnew-peerspawn-corpus-access (Flex Function FS for L1/L2 corpus-mirror); N6 (pre-existing ~20 hex literals in PR #11/#12 vintage detail pages). ADR numbers reserved: W10-4 (future agent_runs.class CHECK widen trigger); W10-5 (unassigned — available for W11+ first SA ADR). Wave 10 MVP COMPLETE. |
PR #21 (squash-merge SHA 0066209; DDL applied parent-side via apply-pr17-ddl.py single-transaction under SET ROLE haleon_aiac_owner; Steward pre-apply attestation PASS + column-order preservation verified 12/12; CLI iter-0 single-pass via short-inline-prompt + .w10/pr17-build-spec.md file-reference per P15; iter-1 fixed 22 API test failures + 3 typecheck errors + 3 LastSeenBadge injections; iter-2 fixed SA-flagged Δ-A (--cp-chart-N CSS tokens definition) + Δ-B (audit.lastSeenBy sp_record_read invocation); post-merge hotfix to app.sp_user_pref_save added #variable_conflict use_column directive for ON CONFLICT ambiguity — P5-class runtime bug not caught in dry-run (DDL apply doesn't invoke SP); SPA via StaticSitesClient.exe direct (upload verb, bool flags as switches not --flag true per current SSC version); Function via az functionapp deployment source config-zip "Deployment was successful"); ship audit 2f16c864-f6fc-405a-84f1-cf53db8a1f50 (chained after PR #16 ed3ca382). Citations: SA-ADR-W10-9 (app.* schema + read-tracking + GDPR) + ADR-W10-1 (HS-4L carve-out) + ADR-W10-2 (touchpoint-feed) + ADR-W10-7 (entity-pointer producer contract scoped to feed-eligible) + ADR-W10-8 (vocabulary boundary); design §11.9 + §11.10 + §11.14 + §11.16 + §18 + §24 + §26.1.6 + §26.1.7. |
2026-06-04 BST |
PR #16 |
F36 Milestone recognition tracker + thank-you drafting (S-complexity, substrate+UI+Function delivery). NEW portfolio.recognitions table (UNIQUE(engagement_id,person_upn,milestone_kind) + CHECK milestone_kind IN {first_workstream_live, first_decision_ratified, first_adr_authored, first_30_day_active, custom} + CHECK status IN {proposed,drafted,sent,skipped} + client_op_id uuid UNIQUE replay anchor + 2 indexes); portfolio.v_recognitions_feed projection (xmin+is_pending+has_*_draft); 4 SECURITY DEFINER SPs in portfolio.* (sp_recognition_flag_milestone / _draft / _mark_sent / _mark_skipped), all HS-4L-routed via cross-schema haleon.sp_create_agent_run per ADR-W10-1, xmin OCC on mutating SPs, replay-first on client_op_id, raw RAISE EXCEPTION USING ERRCODE='P0001' error contract. GRANT EXECUTE×4 + SELECT×2 to w10_app, zero direct DML grants. haleon.v_human_touchpoints additively extended per SA-ADR-W10-7 (Adr c9ff0609-2e4b-41cf-a508-b8acaeb44c35 / Decision 441c3590-e5d6-44e9-8e5b-e29d99a32792; W10-7-watch c81b1d12-... FIRED+FULFILLED via supersession marker bf473f3c-...): new recognitions CASE branch projecting milestone_kind+person_upn+manager_upn+status, plus appended top-level entity_table + entity_id columns (positions 11-12; cols 1-10 + filter semantic preserved verbatim for PR #15 useCases router + all PR #11/12/14 consumers). tRPC: read.recognitions.list/pending/sent + mut.recognitions.flagMilestone/draftThankYou/markSent/markSkipped wired into root.ts (15 mentions, both namespaces). outbound-voice integration = synchronous server-side invoke via apps/api/src/lib/outboundVoice.ts helper (MVP template + TODO for full skill-bridge); drafts NEVER auto-send, human sign-off gate at markSent. SPA: /e/$engagement/recognitions route TanStack-day-1 (risks.index.tsx precedent, NOT decisions legacy), 0 useEffect, 0 var(--color-*), --cp-* tokens via STATUS_COLORS helper, drawer preview before send, [Send peer/manager] + [Skip with reason] actions. Single-source helpers: recognitionStateMachine.ts (STATUSES + LEGAL_EDGES + STATUS_COLORS + MILESTONE_KINDS + MILESTONE_LABELS) + recognitionErrors.ts (vocabulary-boundary mapper per ADR-W10-8 — UI says "Recognition" / "Thank-you" / "Milestone"; error.code/pgCode values NEVER mutated). Substrate smoke 34/34 PASS (smoke-pr16.py: schema/grants 10, flag {happy,idem,hs4l,invalid_kind,uq_collision} 5, draft {peer.happy,idem,stale_row,invalid_variant,manager.happy} 5, markSent {invalid_transition,draft_required,happy,idem} 4, markSkipped {reason_required,happy,idem,stale_row} 4, v_human_touchpoints {row_count,entity_table_top_level,entity_id_top_level,target_table_legacy,target_summary.milestone_kind} 5, pre-checks 1). Vitest 290/290 PASS (211 API + 79 SPA). Typecheck green. Builds green (API 2.5MB, SPA 754KB). 4 functions unchanged (roles+trpc+skillsCommunicationsRecordOutbound+timerRefreshSearch) — recognitions rides the trpc catch-all. Authenticated UI smoke deferred to Charlie (AAD-gated); substrate P5-CONFLICT+P5-IDEM is authoritative functional gate. |
PR #19 (squash-merge SHA a8c17de; DDL applied parent-side via apply-pr16-ddl.py single-transaction under SET ROLE haleon_aiac_owner; Steward post-state attestation chain 641 rows OK + view additive non-breaking + w10_app least-privilege confirmed; CLI iter-0 single-pass via short-inline-prompt + .w10/pr16-build-spec.md file-reference per P15; SPA via StaticSitesClient.exe direct; Function via az functionapp deployment source config-zip deploy id 456f50b1-6230-45ca-b4e2-7180bff748f0 "Deployment was successful"); ship audit ed3ca382-9089-47ef-80ac-52ba91e75b0d. Citations: SA-ADR-W10-7 (entity-pointer promotion) + ADR-W10-1 (HS-4L) + ADR-W10-6 (skills-ingress) + ADR-W10-8 (vocabulary boundary) + design §17 (outbound-capture). |
2026-06-04 BST |
PR #15 |
F31 Workstreams UI (L-complexity, SPA-only delivery on PR #14/14.5 substrate). User-facing vocabulary: Workstream per ADR-W10-8 (Decision 4fb9b6a4-4652-4cbf-9e1e-bf2e9acf7a87) — UI-relabel-only, substrate / tRPC paths / field names / error.code values / route file names / query keys stay use_case/useCase verbatim. Routes: /e/$engagement/use-cases (list + 6-column Kanban toggle with native HTML5 drag-drop) + /e/$engagement/use-cases/$id (detail + transition history + 4-button action row + activity drawer). 6-state machine per F31.5 (mobilising/live/at_risk/wound-down/cancelled/archived); 11 legal edges client-side pre-gated via single helper useCaseStateMachine.ts (no hand-inlined edge lists). 8 reason-required edges (all risk/cancel/wind-down + the at_risk→live recovery) force a 240-char reason modal matching substrate truncation; 3 reason-optional edges (mobilising→live, cancelled→archived, wound-down→archived) skip the modal. 4 write-back modals: create (with "Generate from title" slug button; post-submit CONFLICT catch on duplicate use_case_code), transition reason, assign owner (xmin-guarded), add note. Optimistic mutations per spec contract: onMutate snapshot+flip, onError rollback+toast, onSuccess round-trips newXmin into cache, onSettled invalidates ['useCases', engagement]; CONFLICT (stale_row) drives optimistic-rollback UI. clientOpId generated at drop-commit/click-time (not drag-start/drawer-open) so retry-after-hiccup hits the same idempotency key. expectedXmin passed as plain text from card cache (SP casts ::xid internally; zero ::xid appearances client-side). Active count helper = ACTIVE_STATUSES = ['live','at_risk'] — never undercounts at-risk rows (SA F1 footgun). Single error-mapping helper useCaseErrors.ts with wire-protocol preservation (UI toast says "workstream"; error.code stays code_already_taken/stale_row/etc. verbatim for DevTools+telemetry per SA F7 boundary). Light theme --cp-* tokens only (0 var(--color-*)). TanStack day-1 — 0 useState for server state, 0 useEffect for fetches, trpcClient only inside queryFn/mutationFn (precedent: risks.index.tsx; explicitly does NOT propagate the decisions.index.tsx legacy drift). 54 SPA tests green (was 16, +38: 12 state-machine, 10 error-mapping, 16 route-page covering list/board grouping, DnD edge-gating, optimistic flip + CONFLICT rollback, transition reason-modal mandate, detail history render, create CONFLICT inline-error, transition dropdown legal-targets-only). 196 API tests still green (zero API change). 4 functions unchanged (roles + trpc + skillsCommunicationsRecordOutbound + timerRefreshSearch) — no Function redeploy. No ADR triggered (no agent_runs structural change; W10-7-watch N stays at 2 history-bearing verbs). Deferred (carry-forward): server-side read.useCases.board proc (client-side grouping cheaper at N<500); cancelledAt/archivedAt rendering (needs get SELECT widen + Function redeploy; bundle with future dependencies-link PR); workstream-to-workstream dependencies UI (ADR-W10-8 §4 forward-binding only; own ratification turn when surfaced). |
PR #18 (squash-merge SHA d63be3e; iter-0 single-pass with in-CLI typecheck-fix loop — route tree regen via dev-server tick, get-response-shape correction, ESM require() fix in tests, one error-mapping logic fix); SPA deployed via StaticSitesClient.exe direct (token from swa-w10-clawpilot-uks; bundle index-DeEgRnxM.js); no Function redeploy (4 functions unchanged); ship audit f38acbe5-1f1f-475b-aef0-c2da1b9a3754. Citations: ADR-W10-8 + Decisions 4fb9b6a4-... (relabel) + 28802247-... (F31.5 6-state widen). |
2026-06-04 BST |
PR #14 |
F31 use-case lifecycle (M-complexity, Steward-led substrate): use-case-level tracking one level below engagement/project, DB-schema home per Charlie 2026-06-02 steer. Substrate (parent-applied DDL pre-CLI per Cardinal Rule #3): 3 new tables in haleon schema — use_cases (id PK + use_case_code UNIQUE + title + summary + status use_case_status ENUM {mobilising,live,wound-down} + owner_email + pod_id + lifecycle timestamps proposed_at/went_live_at/wound_down_at + engagement_id + client_op_id), use_case_transitions (append-only state-change log, from_state/to_state/reason_md/actor + audit_run_id FK), and use_case_state_machine seed (legal edges: mobilising→live, live→wound-down). 4 new SPs all SECURITY DEFINER + GRANT EXECUTE w10_app, all routed through haleon.sp_create_agent_run (HS-4L spine, NOT raw agent_runs INSERT): sp_use_case_create (line-1 p_actor_role NOT LIKE 'human:%' → hs4l_violation P0001; raw 23505 on dup use_case_code; replay via client_op_id; seeds status=mobilising), sp_use_case_transition (p_expected_xmin TEXT cast ::xid internally for optimistic concurrency → stale_row; legal-edge check vs state-machine → invalid_transition; reason_required guard; returns new xmin), sp_use_case_add_note (polymorphic haleon.notes write target_table='use_cases'; body_required), sp_use_case_assign_owner (xmin-guarded owner reassignment → stale_row). v_human_touchpoints extended (transition branch links via use_case_transitions.use_case_id subquery; notes branch via target_summary polymorphic pointer) — within ADR-W10-2 §3.1 allowance, NOT ADR-W10-4 trigger. SA forward-binding watch: promote entity_table/entity_id to top-level view columns at the 3rd history-bearing verb. tRPC (API-only, no SPA this PR): NEW useCases.ts split createReadUseCasesRouter (read.useCases.list/get/history) + createMutUseCasesRouter (mut.useCases.create/transition/addNote/assignOwner) wired via per-namespace lazy getters in root.ts (mirrors decisions pattern); all protectedProcedure gated on w10_user role; actorRole='human:'+upn.split('@')[0] derived server-side from ctx.user.upn (NEVER trust input UPN); error mapping hs4l_violation→FORBIDDEN, stale_row/invalid_transition/reason_required/body_required→appropriate tRPC codes; p_expected_xmin passed as plain text (SP casts ::xid, NO $N::xid in query). NO new HTTP function — useCases rides inside the trpc catch-all; Function list stays roles+trpc+skillsCommunicationsRecordOutbound+timerRefreshSearch (4 functions, unchanged). 194 API tests green (was 178, +16: param-array-order contract locks, not.toContain('$4::xid') plain-text-xmin assertions, all error paths). 25/25 substrate smoke PASS (4 SPs × {happy, replay/IDEM, hs4l_violation, invalid_transition, stale_row, reason_required, body_required, view projection}). Authenticated HTTP smoke through SWA front door GREEN: read.useCases.list→200 (schema haleon resolves, returns lifecycle rows), mut.useCases.create→200 (status=mobilising, actorRole=human:smccormick derived from EasyAuth principal — HS-4L actor stamping proven end-to-end through the deployed bundle). ADR-W10-4 NOT triggered (additive tables + additive SPs; view extension within §3.1; no agent_runs structural change). Carry-forwards: W10-7-watch (view entity-pointer promotion at 3rd history verb); SA MIGRATION-NOTES N1/N5a-N5f doc-drift (deferred, non-blocking); still open C10, C11, C13, C14, C15, C15a, C16, C18 partial, C18a, C20 + prior Cnew-* carries. F31.5 amendment (2026-06-04, PR #14.5): SA-ratified additive widen of the use-case state machine from 3 states {mobilising,live,wound-down} to a 6-state superset {+at_risk,cancelled,archived}. Substrate: status CHECK widened to 6 values; 11 edges seeded in use_case_state_machine (adds live↔at_risk cycle, mobilising/live→cancelled, live/at_risk→wound-down, cancelled→archived, wound-down→archived terminal); cancelled_at/archived_at columns added; sp_use_case_transition CREATE OR REPLACE (table-driven — signature/RETURNS unchanged, gains terminal-timestamp stamping). Zero data migration on the 4 live rows. API: widened two zod enums (list.status filter, transition.toState); 196 API tests green (+2 F31.5 contract tests). 21/21 substrate smoke PASS (full lifecycle incl at_risk cycle + cancel/archive paths, reason_required + invalid_transition + terminal guards, replay idempotency). Ratifying Decision 28802247-b233-4fd8-a268-9013719ffedb. NO ADR (additive within ADR-W10-1 + ADR-W10-2 §3.1). Does NOT advance W10-7-watch (no new history-bearing verb). |
PR #16 (squash-merge SHA 7dd82ed; iter-0 single-pass — substrate pre-applied + 25/25 gates GREEN before CLI launch); API-only (no SPA deploy); Function via az functionapp deployment source config-zip (4 functions unchanged — useCases rides trpc catch-all); ship audit ae605e4d-6ee5-439c-8577-b8ffb56d0ae7 (chained after PR #13 ac722457). F31.5 (PR #14.5): PR #17 (squash-merge SHA 91416cc); Function redeployed via az functionapp deployment source config-zip (4 functions unchanged); ship audit 548273ee-5b37-41b0-a93d-eb0a8c925b83 (chained after ae605e4d). |
2026-06-04 BST |
PR #13 |
Search (L-complexity): per-engagement mv_search_index materialized view (§12.2) UNION over decisions / adrs / risks_issues_blockers / use_cases / briefs / communications with (kind, id, title, body, tsv) branch shape; UNIQUE(kind,id) PK + GIN(tsv) + GIN(title gin_trgm_ops); app.corpus_docs branch deferred as Cnew-app-corpus-docs-writer (writer not yet wired). Functions-timer refresh fallback per §22 risk 2 (Flex SKU has no pg_cron). NEW route /search?q=&type=&engagement= with TanStack Table; ⌘K/Ctrl+K palette via Radix Dialog + cmdk with debounced 250ms suggest. Substrate (parent-applied DDL pre-CLI): haleon.mv_search_index + test_engagement_1.mv_search_index MVs materialized; pg_trgm already enabled. 3 new SPs in haleon schema: sp_search_global(p_query text, p_engagements text[], p_kinds text[], p_limit int DEFAULT 50) — uses plainto_tsquery (not websearch_to_tsquery per SA pin), ranks by ts_rank_cd(tsv, q) + 0.1*similarity(title, p_query), fans out server-side over p_engagements array, clamps p_limit<=200, gates access via portfolio.engagements_registry WHERE status='active' (auth.roles_for_user does not exist — MVP placeholder; Cnew-roles-for-user carry for real gating); returns ranked rows with snippet column. sp_search_suggest(p_prefix text, p_limit int DEFAULT 10) — pg_trgm similarity autocomplete on title, patched post-apply to also support short prefixes via ILIKE p_prefix||'%' fallback when similarity threshold yields no hits (G5 fix: 'arc' now returns results). sp_refresh_search_mv(p_engagement text) — REFRESH MATERIALIZED VIEW CONCURRENTLY per engagement, writes class='scout_sweep' audit row, returns run_id. tRPC: read.search.global({query, engagements?, kinds?}) + read.search.suggest({prefix}); NO *_record_read audit (C16 deferral retained). Functions: NEW timerRefreshSearch registered via app.timer('timerRefreshSearch', {schedule:'0 */5 * * * *', handler}) — fans out sp_refresh_search_mv over ['haleon','test_engagement_1'] via Promise.allSettled. Final Function list: roles+trpc+skillsCommunicationsRecordOutbound+timerRefreshSearch (4 functions, was 3). SPA: NEW /search route with TanStack Table from day-1 + URL search-params for q/type/engagement; ⌘K/Ctrl+K palette mounted in header stub via cmdk + Radix Dialog; type → debounced 250ms → read.search.suggest; Enter → navigate to /search?q=...; arrow-key+Enter → navigate to hit detail. Ship-fix lesson (P13): new app.timer(...) registration in functions/timerRefreshSearch.ts was tree-shaken because main.ts didn't import the file — deploy reported success but App Host showed only 3 of 4 functions. One-line fix commit a73a80d added import './functions/timerRefreshSearch'; rebuild + redeploy registered all 4. Any new function file MUST be imported by main.ts or it disappears from the bundle — add to w10-build SKILL P-lessons. ADR-W10-4 NOT triggered (additive MVs + additive SPs; no agent_runs structural change). Carry-forwards: NEW Cnew-app-corpus-docs-writer (canon-watch CA Job to populate app.corpus_docs; until shipped, MV UNION is 6 branches not 7), Cnew-roles-for-user (real access-control gating once auth.roles_for_user exists — MVP uses engagements_registry status='active' placeholder); still open C10, C11, C13, C14, C15, C15a, C16, C18 partial, C18a, C20, Cnew-checkoff-orphan-reconcile, Cnew-report-decide-inline, Cnew-usermenu-wire, Cnew-comm-status-axis, Cnew-kernel-purity-trpc-skills, Cnew-skill-bearer-config, Cnew-mark-consumed-metadata-noop, Cnew-mark-consumed-idem-debt, Cnew-prefix-cache-doc, cross-schema view-join gap. |
PR #15 (merge SHA 6baaf2d for content + fix SHA a73a80d for main.ts import); SPA via StaticSitesClient.exe direct 51s; Function via az functionapp deployment source config-zip OneDeploy cc6c59e3-e295-4602-88bc-6be78a38d92b (4 functions registered roles+trpc+skillsCommunicationsRecordOutbound+timerRefreshSearch); ship audit ac722457-37fb-471a-82c4-f6f7d44b4ece. |
2026-06-04 BST |
PR #12 |
Reports check-off write-back + viewer affordances: §11.4 + §12.2. Server-emitted ▢{key}/▣{key} glyphs in daily-status + incident-postmortem report bodies become click-target <CheckOffButton> via custom markdown text-node walker; [decide →#<id>] tokens become pure-navigation links to decision detail (W4 defer inline creation). Add-note drawer mirroring PR #9/#11. Full TanStack refactor of pre-existing useState/useEffect/trpcClient route (drift-bomb predating PR #11 mandate). Substrate (parent-applied DDL pre-CLI per Cardinal Rule #3): haleon.checkoffs stub FULLY-FORMED per §12.2 (id PK + target_table/target_id/item_key NN + checked NN + checked_by NN + checked_at NN DEFAULT now() + audit_run_id FK + client_op_id NN UNIQUE + UNIQUE(target_table,target_id,item_key)) — zero ALTERs. 2 new SPs all in haleon schema: sp_report_checkoff_toggle (NEW dual-param shape per SA AP1: p_target_type + p_target_id composed at SP body to target_id := p_target_type||':'||p_target_id via : separator pin; target_table='reports' constant; line-1 p_actor_role NOT LIKE 'human:%' → hs4l_violation P0001; item_key_required guard; replay-FIRST via UNIQUE(client_op_id) inside same TX with UPSERT per SA AP4 pin; ON CONFLICT (target_table, target_id, item_key) DO UPDATE with #variable_conflict use_column pragma + table alias to resolve OUT-param ambiguity; returns full TABLE shape (run_id, checkoff_id, target_table, target_id, item_key, checked, checked_by, checked_at, actor_role); payload key 'pk'; verb 'ui.check_off'; widening required DROP+CREATE not REPLACE per Steward Option 1 — old sp_report_check_off SP retained untouched for back-compat); sp_report_add_note (mirrors sp_comm_add_note exactly; line-1 hs4l_violation; body_required guard; replay via UNIQUE(client_op_id) on haleon.notes; writes haleon.notes(target_table='reports:'||p_target_type, target_id=p_target_id) polymorphic per AP5; verb 'ui.add_note'). All SECURITY DEFINER + GRANT EXECUTE w10_app. v_human_touchpoints checkoffs branch extended per AP6: now projects {target_table, target_id, item_key, checked, checked_by, checked_at} — within ADR-W10-2 §3.1 allowance, NOT ADR-W10-4 trigger. Q9 carry CLOSED (4-SP sweep): haleon.sp_record_read, test_engagement_1.sp_record_read, haleon.sp_document_record_read_denied, test_engagement_1.sp_document_record_read_denied now all write payload key 'pk' (was 'id') per view payload->>'pk' convention — combined with PR #11 sp_document_record_read fix, all 6 read.row family SPs now aligned; every read.row/read.row.denied touchpoint surfaces with non-NULL target_summary. tRPC reports.ts split into createReadReportsRouter + createMutReportsRouter wired via per-namespace getters in root.ts (mirrors PR #11 pattern): read.reports.get extended to return checkoffs: [{itemKey, checked, checkedBy, checkedAt}] sibling array (SA W1 — state authoritative, glyphs presentational) via NEW fetchCheckoffsForReport(query, slug, id) helper hard-capped 200 rows; handleDailyStatus + handleIncidentPostmortem emit deterministic ▢{dailystatus-touchpoint/N} / ▢{incident-rib/N} tokens per per-report-type KEY_MANIFEST constant (SA AP2 single source of truth pin); state look-up at render time flips emitted glyph from ▢ to ▣ when checkoff row says checked; mut.reports.checkOff derives actorUpn from ctx.user.upn ↔ constructs actorRole='human:'+upn.split('@')[0] server-side (NEVER trust input UPN); maps hs4l_violation→FORBIDDEN, item_key_required→BAD_REQUEST; mut.reports.addNote same auth derivation + body_required→BAD_REQUEST. NO new HTTP function (mutations are tRPC procs); Function list stays roles+trpc+skillsCommunicationsRecordOutbound. SPA e.$engagement.reports.$type.$id.tsx REFACTOR: deletes useState/useEffect/trpcClient.read.reports.get.query direct calls; replaces with useQuery({queryKey:['reports','get',engagement,type,idNum], queryFn:() => trpcClient.read.reports.get.query({...})}); useMutation for checkOff (optimistic flip in onMutate via queryClient.setQueryData + rollback in onError + invalidate in onSettled — mirrors PR #4 addNote pattern per SA W3) + addNote; custom React markdown component-walker parses text-nodes for regex /(▢\{[a-z][a-z0-9_/-]+\}|▣\{[a-z][a-z0-9_/-]+\}|\[decide →#[a-zA-Z0-9-]+\])/g → replaces with <CheckOffButton> (renders glyph with aria-pressed + onClick → mutate with crypto.randomUUID() as clientOpId AT CLICK TIME per SA W3) or <Link to="/e/$engagement/decisions/$id" search={{from:'report:'+type+':'+id}}> (pure navigation per W4); add-note drawer button in header opens drawer with controlled <textarea> → save mutation with clientOpId generated AT DRAWER-OPEN per SA W3 distinction. TanStack from day-1 in all new code (useQuery/useMutation 4+; queryClient.invalidateQueries on save). P7 substrate probes 14/14 PASS (G1 happy checkoff_toggle returning full TABLE shape, G2 hs4l_violation skill caller, G3 item_key_required empty-string, G4 IDEM same client_op_id returns same run_id+checkoff_id, G5 UPSERT flip false→true→false via fresh client_op_ids, G6 target_id composite stored as 'reports' / 'slug:id', G7 view projects checked_by+checked_at, G8 happy add_note, G9 hs4l skill caller, G10 body_required, G11 IDEM, G12 polymorphic target_table='reports:<slug>' on notes row, G13 Q9 haleon.sp_record_read payload uses 'pk', G14 te1.sp_record_read same). 172 API tests green (was 163, +9 hits target: 3 checkoffs-array sibling shape + 4 checkOff happy/replay/forbidden/bad-request + 2 addNote happy/replay) + 12 SPA tests (was 9, +3 hits target: CheckOffButton render with initial state from sibling array, mutation firing with optimistic cache flip, drawer lifecycle). Drift-bombs GREEN: var(--color-)=0 in modified files, useEffect=0 in refactored route, useState only for drawerOpen+textarea+drawerClientOpId (UI presentation, allowed), trpcClient only inside queryFn/mutationFn. ADR-W10-4 NOT triggered (additive SPs; view extension within ADR-W10-2 §3.1 allowance; no agent_runs structural change; no new role/verb taxonomy). Carry-forwards: CLOSED read-row-target-pk-key-mismatch (full 6-SP family sweep complete); NEW Cnew-checkoff-orphan-reconcile (when report-type key schema bumps risks/1→critical_risks/1, prior checkoffs orphaned — future migration helper SP per SA AP2 sub), Cnew-report-decide-inline (inline [decide →] SP-driven creation deferred per SA W4 — mut.decisions.createFromReportItem future PR), Cnew-usermenu-wire (close C18 fully by wiring SPA UserMenu to read.auth.me — trivial follow-up); cross-schema view-join gap carry scope grew to {communications, checkoffs} per AP7; still open C10, C11, C13, C14, C15, C15a, C16, C18 partial, C18a, C20, Cnew-mark-consumed-metadata-noop, Cnew-mark-consumed-idem-debt, Cnew-prefix-cache-doc, Cnew-comm-status-axis, Cnew-kernel-purity-trpc-skills, Cnew-skill-bearer-config. |
PR #14 (merge SHA 59757d2; iter-0 1637574 single-pass — substrate pre-applied + 14/14 gates GREEN before CLI launch); SPA via StaticSitesClient.exe direct 30s; Function via az functionapp deployment source config-zip OneDeploy d3092999-2ebe-49f1-9a8a-8a5b508696b0 (3 functions registered roles+trpc+skillsCommunicationsRecordOutbound unchanged); 5 DDL audit cross-links (0c0c095f sp_report_checkoff_toggle + 23d109c0 sp_report_add_note + c1dad0db view extend + 58992d06 Q9 sweep family + c0df9435 variable_conflict patch) + ship audit 91f67447-47f2-4093-984b-ca7d806422d8. |
2026-06-04 BST |
PR #11 |
Communications surface: §11.13 + §17. Cards-list + filter chips (channel/direction/date) + detail viewer with inbound-paste textarea + addNote drawer + activity drawer. Skills-tier ingress endpoint /api/skills/communications/record-outbound (path-distinct Function, JWT bearer middleware, ADR-W10-6). New auth.me proc closes C18. Read-row payload-key bug fixed ('id'→'pk') closes Steward critical from PR #10. Inherits PR #7 chrome + PR #9-#10 TanStack discipline. Substrate (parent-applied DDL pre-CLI per Cardinal Rule #3): ALTER haleon.communications ADD direction text NN DEFAULT 'outbound' CHECK ('outbound','inbound') + ADD related_run_id uuid NULL + ADD CHECK channel IN ('m365_chat','email','m365_meeting','other') (locked closed-set per SA AP1 amend — teams redundant with m365_chat, phone graveyard; 'other' absorbs futures via metadata.subchannel) + 3 supporting indexes (direction, related_decision, related_risk). 3 new SPs all in haleon schema (view reads from haleon.communications only — cross-schema view-join gap from PR #10 deferred): sp_comm_record_outbound (dual-role-param shape per SA CP1' — p_sender_role validated against existing 5-label CHECK at SP entry via SA P1 early invalid_sender_role RAISE; p_actor_role line-1 guard NOT LIKE 'skill:%' → hs4l_skill_guard P0001; idempotency via UNIQUE(client_op_id) replay; writes agent_runs.agent_name=p_actor_role with verb 'skill.comm.record_outbound' tier 'cross_cutting_capability'); sp_comm_paste_inbound (line-1 human:% guard → hs4l_role_guard; body_required guard; replay-check FIRST per PR #9 canonical; FOR UPDATE lock + xmin compare → stale_row P0001 per Steward Q6 canonical; merges response_capture jsonb via || concat preserving prior keys, sets {source, body_md, pasted_at, pasted_by}); sp_comm_add_note (mirrors sp_risk_add_note exactly — line-1 hs4l_violation, replay via UNIQUE(client_op_id) on haleon.notes, writes haleon.notes(target_table='communications', target_id=p_comm_id::text) with verb 'ui.add_note'). All SECURITY DEFINER + GRANT EXECUTE w10_app + payload key 'pk' (NOT 'id') per view convention. v_human_touchpoints communications branch extended additively: now projects {channel, recipient_upn, subject, target_table, target_id, direction, sender_role, sent_at, related_decision, related_risk} — within ADR-W10-2 §3.1 allowance, NOT ADR-W10-4 trigger. Q9 carry CLOSED inline (2-line fix): both haleon.sp_document_record_read + test_engagement_1.sp_document_record_read now write payload key 'pk' (was 'id') so view payload->>'pk' extraction surfaces target_pk correctly — every read.row touchpoint now lands with non-NULL target_summary. tRPC 1 new router (communications.ts): read.list({engagement, channels?, directions?, dateFrom?, dateTo?, relatedDecisionId?, relatedRiskId?, relatedRunId?}) ORDER BY sent_at DESC LIMIT 200, NO *_record_read audit (C16 deferral retained); read.get({engagement, id}) single row + xmin + history slice from view; mut.pasteInbound calls sp_comm_paste_inbound with stale_row→CONFLICT/hs4l→FORBIDDEN/body_required→BAD_REQUEST mapping; mut.addNote calls sp_comm_add_note. auth.me proc (NEW, closes C18): returns {upn, roles, displayName?} parsed from x-ms-client-principal, no audit. Skills-tier ingress per ADR-W10-6 (NEW, SA-owned, ratified pre-merge): NEW Function apps/api/src/functions/skills.communications.recordOutbound.ts registered as app.http('skillsCommunicationsRecordOutbound', {route:'skills/communications/record-outbound'}) at authLevel:'anonymous' with own bearer JWT verify (iss + aud + exp + sub-allowlist via env SKILL_JWT_*); short-circuits to 503 skill_ingress_not_configured when env unset (PR #11 ship default; m-skills/ wrap configures); maps hs4l_skill_guard→403, invalid_sender_role→400, 23514→400, stale_row→409, success→201 with {success, runId, id, senderRole, actorRole}. ADR-W10-6 canonical pattern: path-distinct partition (NOT internal.* tRPC namespace — would type-leak via AppRouter export); dual-role-param (semantic p_sender_role vs technical p_actor_role) — preserves trust surface of existing sender_role CHECK; 'skill.<verb>' action taxonomy distinct from 'ui.*'; 'skill:<skill_name>' role-prefix — view WHERE role LIKE 'human:%' naturally excludes skill rows without view change; symmetric HS-4L variant guard (skill SPs reject 'human:%'+'peer:%'; human SPs reject 'skill:%'); pairs with ADR-W10-5 (read-only dual-path) as the read-then-write counterpart. SPA: 2 new routes — e.$engagement.communications.index.tsx (cards list, channel chips + direction toggle + date range, useQuery key ['communications','list',engagement,...filters], empty state) + e.$engagement.communications.$id.tsx (detail viewer; two-col layout body + sidebar; inbound-paste textarea gated by direction='outbound' AND response_capture is null/empty; addNote drawer; activity drawer from useQuery({queryKey:['communications','get',...]}) history); Communications nav flipped from nav-disabled span to live <Link activeProps> in e.$engagement.tsx (3 future tabs — Use Cases / Recognition / Library / Metrics / Audit — stay disabled). TanStack from day-1 (useQuery/useMutation 8+ across files; queryClient.invalidateQueries on success). P7 substrate probes 14/14 PASS (G1 happy record_outbound, G2 bad-channel CHECK 23514, G3 bad-sender SA P1 P0001 invalid_sender_role, G4 human-guard SKILL_GUARD P0001, G5 IDEM same run_id+comm_id; G6 happy paste_inbound merged response_capture, G7 stale_row P0001, G8 body_required P0001, G9 skill caller rejected from human SP, G10 IDEM same run_id; G11 happy add_note 1 note row + 1 audit, G12 IDEM same; G13 view shape projects direction+sender_role+sent_at+related_* correctly; G14 read.row payload key 'pk' = '1' not NULL proving Q9 fix). 163 API tests green (was 145, +18 exceeds +15 target: list filters / get / pasteInbound happy+stale+replay+forbidden / addNote happy+idem / recordOutbound 401/403/503/201+replay / auth.me) + 9 SPA tests (was 4, +5 exceeds +3 target). Drift-bombs GREEN: var(--color-)=0 in new files, useEffect=0, trpcClient only inside queryFn/mutationFn, routeTree.gen regenerated with 2 new routes. ADR-W10-4 NOT triggered (additive ALTERs on existing column shape; new SPs additive; view extension within ADR-W10-2 §3.1 allowance; no agent_runs structural change). Carry-forwards: CLOSED C18 (auth.me wired) + read-row-target-pk-key-mismatch (inline 2-line SP body fix); PARTIAL CLOSE Cnew-comm-skill-wrap (server endpoint shipped; m-skills/ wrap ships separately); NEW Cnew-comm-status-axis (draft/sent/received/archived status axis deferred per SA AP2 — if recognition surface needs it, PR #16+), Cnew-kernel-purity-trpc-skills (forbid apps/api/src/trpc/↔apps/api/src/skills/ cross-imports per ADR-W10-6 §3.1 — F5 follow-up), Cnew-skill-bearer-config (SKILL_JWT_* env settings populated when m-skills/ wrap ships); still open C10, C11, C13, C14, C15, C15a, C16, C18a, C20, Cnew-mark-consumed-metadata-noop, Cnew-mark-consumed-idem-debt, Cnew-prefix-cache-doc, cross-schema view-join gap (Steward Q10 #1 from PR #10). |
PR #13 (merge SHA 241884b; iter-0 102dc72 single-pass — substrate pre-applied + 14/14 gates GREEN before CLI launch); SPA via StaticSitesClient.exe direct 16s (P10: no --env); Function via az functionapp deployment source config-zip OneDeploy f47290bf-d75a-4061-9802-a5a06ad44208 (3 functions registered roles+trpc+skillsCommunicationsRecordOutbound); 4 DDL audit cross-links (0f27e18f ALTERs + b0c2f364 sp_comm family + 2da931e8 view extend + 4th for read_row pk fix) + ship audit 1dfd8ece-3f6b-4787-93cf-80758cf85a6d; ADR-W10-6 ratified at ~/.copilot/m-policies/adr/SA-ADR-W10-6-SKILLS-INGRESS.md. |
2026-06-04 BST |
PR #10 |
Documents UX completion: §11.11 left-tree nav (briefs / docs / skills) + sensitivity-chip filter + Confidential/Regulated confirm-click ack-gate + inline [mark consumed] (TanStack mutation) + catch-all detail viewer + canon-watch CA Job ingest contract + 4 role-overlay m_read_canon flips optional→required. Inherits PR #7 chrome + extends PR #6 documents substrate. SPA route reshape: PR #6 flat-table at e.$engagement.documents.index.tsx rewritten as empty-state "pick a doc" component; NEW layout e.$engagement.documents.tsx hosts <Outlet/> with left tree (lazy-loaded via TanStack useQuery per node) + top sensitivity-chip set (4 chips General/Internal/Confidential-Customer/Regulated-Restricted, default ALL ACTIVE, subtractive client-side filter per SA AP2); NEW catch-all e.$engagement.documents.$.tsx renders selected document with path-injection defence (regex /^[A-Za-z0-9_\-./]+$/, no leading /, no .. per SA AP14) + metadata sidebar (sensitivity / kind / last_seen / source path) + inline [mark consumed] button using useMutation + queryClient.invalidateQueries({queryKey:['documents']}) on success; old e.$engagement.documents.$id.tsx DELETED. Confidential/Regulated confirm-click gate (SA AP15): SPA renders STUB CARD with sensitivity banner + summary + [open in source ↗] button on Confidential-Customer/Regulated-Restricted classes; click sets local ackConfidential=true, body fetch fires via extended read.documents.body({path, ackConfidential:true}); server-side BAD_REQUEST 'ack_required' P0001 thrown BEFORE clearance check when flag missing/false (NO audit row on cancel/no-ack); cleared+acked path runs existing requireSensitivityClearance + sp_document_record_read success audit + last_seen_at bump (PR #6 §26.2.5 chokepoint preserved per SA ADR-W10-3 conformance); denied path runs new sp_document_record_read_denied with outcome='denied' BEFORE FORBIDDEN 403 throw (closes PR #6 E2 carry per SA AP10). 4 new tRPC procs (sub-router pattern under read.documents): read.documents.tree.root({engagement}) returns [{path,type:'dir',maxSensitivity,hasChildren}] from SELECT DISTINCT split_part(storage_path,'/',1) + max-sensitivity GROUP BY (LIMIT 50 defensive); read.documents.tree.children({engagement, parentPath}) lazy-expands with server-side maxDepth=2 guard (parentPath with >2 segments → BAD_REQUEST 'max_depth_exceeded' per SA AP1a); read.documents.metadata({engagement, path}) returns {id,title,kind,sensitivityLabel,storageBackend,storagePath,lastSeenAt,summary} NO-AUDIT (passive metadata only); read.documents.body EXTENDED to accept EITHER documentId (PR #6 legacy) OR path (new, resolves via UNIQUE (storage_backend, storage_path)) PLUS new ackConfidential:boolean field. dispatchByBackend prefix routing: ^(briefs|docs|skills)/ → stclawpilotcorpusuks account container split from path (canon-watch surface); ^engagement-demo/ → existing stw10apiuks/documents (PR #6 legacy preserved); uses DefaultAzureCredential with test injection via __setBlobServiceForTest. canon-watch CA Job provisioned at caj-clawpilot-canon-watch-vnet on cae-clawpilot-vnet-uks (cron 0 3 * * * UTC, one hour after sibling caj-clawpilot-corpus-sync-vnet per §16.1) mirroring sibling shape: azure-cli:latest base + SCRIPT_B64 secret pattern + dual UAMI (mi-clawpilot-corpus-flipper opens/closes network window, mi-clawpilot-corpus-rw blob read + PG INSERT via AAD-passwordless), replicaTimeout=600, command installs postgresql-client then decodes+executes bash script. Walks 3 containers (briefs, docs, skills — corpus storage is stclawpilotcorpusuks with publicNetworkAccess=Disabled, NOT design-doc's notional engagement-docs), per .md file: SHA256 + YAML-frontmatter sensitivity extract (fallback Internal) + H1 title extract + UPSERT into <eng>.documents ON CONFLICT (storage_backend, storage_path) DO UPDATE gated by sha256_or_etag IS DISTINCT FROM EXCLUDED.sha256_or_etag OR last_seen_at < sweep_start_ts (SHA-gate idempotency per SA AP5 + Steward heartbeat soft-delete acceptance per A9); soft-delete via last_seen_at < sweep_start_ts heartbeat (NO additional column, NO DDL on documents table); writes ONE agent_runs class='scout_sweep' triggered_by='canon_watch.scan' row per run with payload {run_id, files_scanned, rows_upserted, rows_soft_deleted, open_window_sec}. Substrate additives (parent-applied DDL pre-CLI per Cardinal Rule #3): haleon.v_human_touchpoints documents-branch extended to project {title,kind,sensitivity_label,target_table,target_id,consumed_by:metadata->'consumed_by'} — additive per ADR-W10-2 §3.1 (non-breaking CREATE OR REPLACE, column count preserved at 10); test_engagement_1.sp_document_record_read_denied created (closes PR #6 E2 gap where router called missing SP → would have thrown 42883); 3 fixture documents seeded into test_engagement_1.documents matching canon-watch path convention (id=1 briefs/sa-brief/SKILL.md Internal; id=2 skills/w10-build/SKILL.md Internal; id=3 docs/reporting-design.md Confidential-Customer) + 3 DDL audit cross-links (9076d10c-88f3-4524-8d20-b9ff90609fc1 view extension + 99db419f-49e7-4016-bbc5-17b716b89525 denied SP + e031f544-58bb-421a-b3fa-25bf6c923b38 fixtures). NO ALTER TABLE on documents. 4 role-overlay flips applied per SA AP7 sequencing: m_read_canon moved from optional → required in CHIEFOFSTAFF.overlay.md, SOLUTIONARCHITECT.overlay.md, STEWARD.overlay.md, LIBRARY-CURATOR.overlay.md with PR #10 comment naming the in-repo shim at apps/api/src/m_read_canon.ts as authoritative host-side resolution. P7 substrate probes 6/9 PASS + 3 tracked-debt-OK (G1 tree-root 3 dirs project correctly; G2 children('briefs') 1 file; G3 metadata id=1 OK; G4 sp_document_record_read +1 audit + last_seen bumped; G6 sp_document_record_read_denied +1 audit with outcome='denied'; G7 sp_document_mark_consumed +1 audit but consumed_by metadata write silently no-ops — PR #6 SP jsonb_set deviation on missing intermediate, NEW carry-forward Cnew-mark-consumed-metadata-noop; G8 view shape projection correct but TE1-to-haleon cross-schema gap surfaces NULL consumed_by — pre-acked; G9 markConsumed not client_op_id-idempotent — pre-existing Cnew-mark-consumed-idem-debt). 145 API tests green (was 129, +16 documents.test.ts cases: tree-root happy/empty/single-dir/3-dirs ordered, tree-children happy + maxDepth>2 reject, metadata happy + NOT_FOUND, body ackConfidential=false on Confidential→BAD_REQUEST + zero audits, body ackConfidential=true + not-cleared→FORBIDDEN + 1 denied audit, body ackConfidential=true + cleared→success + 1 read audit + last_seen bumped, body General/Internal ignore ack, path-input alias resolves identically to documentId-input, markConsumed verb='ui.mark_canon_consumed' regression guard, dispatchByBackend prefix routing) + 4 SPA tests (was 2, +2: catch-all confirm-click flips ackConfidential, layout chip-toggle filters tree rows). Drift-bombs GREEN: var(--color-)=0 SPA-wide, useEffect in documents routes=0, useState in documents routes=5 (all pure UI state — ackConfidential flag, activeSensitivity Set, expandedDirs Set; NO data-fetching state per SA WP2 bless), trpcClient. in documents routes=5 (all inside queryFn/mutationFn callbacks per SA WP1 bless), useQuery|useMutation=10, old $id.tsx deleted. ADR-W10-4 NOT triggered (view CASE projection extension is non-structural per §3.1; new SP is additive; no agent_runs column or shape change). Carry-forwards: CLOSED C12 (blob walker contract live as canon-watch CA Job) + Cnew-tanstack-debt-documents (migration complete) + Cnew-AP10 (403 audit landing via denied SP) + Cnew-AP11 (last_seen success-only bump verified); NEW C18a (mi-clawpilot-corpus-rw PG role mapping needed for canon-watch first scheduled run at 03:00 UTC — manual smoke posture for PR #10 only, AAD-passwordless PG GRANT is sister-task for follow-up PR), Cnew-mark-consumed-metadata-noop (PR #6 SP jsonb_set(metadata, ARRAY['consumed_by', upn], ...) silently no-ops because intermediate consumed_by key doesn't exist on first mark; audit row + last_seen bump still fire correctly — only the on-doc metadata side-effect drops), Cnew-prefix-cache-doc (document the invalidateQueries({queryKey:['documents']}) prefix-match cascade convention at first-use comment per SA P1), read-row-target-pk-key-mismatch (Steward critical finding: sp_document_record_read writes payload key 'id' but v_human_touchpoints extracts 'pk' → every read.row touchpoint surfaces with NULL target_pk & NULL target_summary; sp_document_mark_consumed uses correct 'pk' key; one-line fix in SP OR tolerate-both in view base — deferred); still open C10, C11, C13, C14, C15, C15a, C16, C18, C20, Cnew-mark-consumed-idem-debt, Cnew-add-note-guard-verify, Cnew-revoke-public, Cnew-ratification-class, Cnew-smoke-param, Cnew-test-symmetry, Cnew-decay-non-touch. |
PR #12 (merge SHA bb0aa52; iter-0 c247bb5 single-pass — no peer-review iter required); SPA via StaticSitesClient.exe direct (P10: no --env); Function via az functionapp deployment source config-zip OneDeploy cdc7ad4e-fbe7-4961-8ea2-3977375b76c4 (P9 + Flex Consumption: zip via [ZipFile]::Open + CreateEntryFromFile preserving dist-bundle/ path; 2 functions registered roles+trpc); canon-watch CA Job caj-clawpilot-canon-watch-vnet provisioned via YAML spec (provisioningState=Succeeded); 3 DDL audit cross-links (9076d10c view + 99db419f denied SP + e031f544 fixtures) + ship audit 46e62120-cd69-40e2-b88e-c566642361a3. |
2026-06-04 BST |
PR #9 |
Risks register: list + 5×5 board + detail with decay countdown + activity drawer + HS-4L write-back fan-out (Start mitigation / Escalate / Assign owner / Add note). Inherits PR #7 chrome + PR #8 fan-out pattern. Risks viewer (§11.7): list route e.$engagement.risks.index.tsx ordered risk_score DESC NULLS LAST, decay_at ASC NULLS LAST with status chip (open→--cp-warning, mitigating→--cp-link, closed→--cp-text-muted, realised→red-strikethrough "REALISED" badge per SA AP4), severity / likelihood / risk_score columns, type chip, owner (ownerEmail), decay countdown column; board view toggle renders 5×5 heatmap with severity = Y axis (vertical ascending bottom→top), likelihood = X axis (horizontal ascending left→right) per SA AP2, cell = read-only count chip of risks with type='risk' AND status IN ('open','mitigating') excluding realised/closed, colour-zoned green/amber/red. Detail route e.$engagement.risks.$id.tsx renders decay countdown derived client-side from decayAt - now() (display-only per SA RX3 — SPs MUST NOT write decay_at) showing "Review due in N days" / "Overdue by N days" (red) / "No decay set"; activity drawer surfaces history array from v_human_touchpoints view (AP1 source), newest-first. Risks tab in engagement nav flipped from nav-disabled span to live <Link activeProps> (other 8 future tabs stay disabled). 3 new tRPC read procs: read.risks.list / read.risks.get / read.risks.board; all filter WHERE type='risk' at read layer (SA RX2 tri-type semantics — risks_issues_blockers table holds risks + issues + blockers, issues/blockers deferred to later PRs); list/board 42P01→[]/{cells:[]}, get 42P01→NOT_FOUND; no *_record_read audit (C16 deferral preserved). Projects substrate engagement_id as DTO engagementCode (SA AP3) and owner_email as DTO ownerEmail (SA RX6 — deliberately NOT ownerUpn to avoid symmetric confusion with decisions' real owner_upn column). 3 new HS-4L stored procedures (parent-applied DDL pre-CLI per Cardinal Rule #3): sp_risk_start_mitigation flips status='open' → 'mitigating' with legal-transition gate (OLD.status='open' → P0001 illegal_transition otherwise — closure/realisation deferred to PR #10+ per SA RX1); sp_risk_escalate bumps severity by 1 with cap=5 (P0001 severity_ceiling at ceiling per SA RX4), legal from status IN ('open','mitigating'), requires reason_md (NOT NULL, len>0 → P0001 reason_required) persisted to agent_runs.payload.reason_md ONLY (SA RX4b — mirrors PR #8 R1, no column add, no ADR-W10-4 trigger), does NOT bump likelihood and does NOT flip status, one audit row per gesture with payload {severity_before, severity_after, reason_md}; sp_risk_assign_owner writes owner_email = lower(p_owner_email) with case-insensitive whitelist check (NOT (lower(p_owner_email) = ANY(SELECT lower(unnest(haleon.fn_valid_owners())))) → P0001 invalid_owner per SA RX6 — fn_valid_owners() stores lowercase UPNs, raw comparison would silently reject mixed-case input). 4th SP reused not re-authored: sp_risk_add_note already shipped pre-PR #9 (HS-4L compliant, writes generic haleon.notes table) — new mut.risks.addNote mutation calls existing SP (SA RX5). All 3 new SPs SECURITY DEFINER + GRANT EXECUTE w10_app + single-TX + role-guard human:% + xmin OCC (stale_row P0001 on mismatch) + unbounded client_op_id idempotency (JOIN agent_run_payloads p1.payload->>'client_op_id', scoped AND p1.payload->>'table'='risks_issues_blockers') + alias+qualify UPDATE…RETURNING r.* INTO v_row per P7-PR #5 ambig-col lesson + NEVER write risk_score (GENERATED STORED (severity*likelihood), auto-recomputes) or decay_at. Audit emission via haleon.sp_create_agent_run(... 'turn', ...) + separate INSERT INTO agent_run_payloads(run_id, payload) carrying {table, pk, target_table, target_id, after: to_jsonb(v_row), client_op_id, ...verb-specific keys}. 4 new tRPC mutations under mut.risks: startMitigation / escalate (no likelihood/severity inputs — server bumps severity per RX4) / assignOwner / addNote; identity from ctx.user never input (ADR-W10-1 §3.6); error map locked 8-class: stale_row→CONFLICT 409 / hs4l_role_guard→FORBIDDEN 403 / illegal_transition+severity_ceiling+reason_required+invalid_owner→BAD_REQUEST 400 / not_found+42P01→NOT_FOUND 404. SPA risk-detail fan-out: 4 action buttons gated on status ('open'→Start mitigation only, open|mitigating→Escalate via drawer with mandatory reason textarea, open|mitigating→Assign owner inline form free-text MVP, open|mitigating→Add note drawer; closed|realised hides Actions section per AP4); both new routes migrated to TanStack Query (useQuery reads + useMutation + queryClient.invalidateQueries on success, fan-out invalidates 3 query keys: ['risks',engagement,id] + ['risks',engagement] + ['risks','board',engagement]) — first useMutation precedent in the SPA. Additive v_human_touchpoints view extension (CoS AP1 + Steward attestation): risk-branch CASE now projects target_id, target_table, likelihood, risk_score alongside existing title/severity/status — non-breaking CREATE OR REPLACE per ADR-W10-2 §3.1 projection-append allowance, NOT an ADR-W10-4 trigger. New verb taxonomy entries per SA AP5: ui.start_mitigation / ui.escalate / ui.assign_owner (within ADR-W10-1 §3.2 extension allowance, no amendment needed). P7 substrate probes 13/13 PASS (3×happy [start_mitigation/escalate/assign_owner] + 6×negative [CONFLICT/FORBIDDEN/ILLEGAL_TRANSITION/SEVERITY_CEILING/REASON_REQUIRED/INVALID_OWNER all P0001] + 1×positive [uppercase UPN KATIE.EDWARDS@microsoft.com → lowercased katie.edwards@microsoft.com proving normalization] + 1×IDEM [replay same client_op_id → same run_id, +1 agent_runs row only] + 2×view shape [3 touchpoints surface for fixture with target_summary carrying target_id/likelihood/risk_score]). 129 API tests green (was 107, +22: 21 risks router tests + 1 ONE_AUDIT_ROW assertion for sp_risk_escalate per SA WP3) + 2 SPA tests. ADR-W10-4 NOT triggered (no ALTER TABLE, all columns already present on risks_issues_blockers; new SPs additive on existing column shape; view extension within ADR-W10-2 §3.1 allowance; no agent_runs structural change). Substrate findings (Steward read-only probes): the real table is haleon.risks_issues_blockers (tri-type RIB register), NOT haleon.risks — assumed-column drift from design doc resolved without ALTER (assumed owner_upn → actual owner_email; assumed body_md → actual description; assumed last_reviewed_at+review_cadence_days → actual decay_at+escalation_threshold; assumed engagement_code → actual engagement_id); rib_status enum is (open, mitigating, closed, realised) with NO 'mitigated' value (forced SP rename to sp_risk_start_mitigation); risk_score is GENERATED STORED (must never be written by SPs); fn_valid_owners() stores lowercase UPNs (forced case-insensitive comparison in assign_owner SP); sp_risk_add_note + generic haleon.notes table already shipped (no new notes substrate). Fixture risk seeded for smoke: id=abb5a0c6-655f-4e2d-9e07-5dce71d2e8fb status=mitigating severity=4 likelihood=3 risk_score=12 owner_email='smccormick@microsoft.com' (post-smoke terminal state). Carry-forwards: Cnew (RX3) deliberate non-touch of decay_at in PR #9 SPs — explicit "I reviewed this" SP is future-PR concern; Cnew (RX5) existing sp_risk_add_note HS-4L line-1 human:% guard verification (iter-1 test or Steward sister-task attestation); Cnew (AP5) verb taxonomy extended with 3 new ui.* verbs — search-anchor for archaeology; Cnew-tanstack-debt decisions/reports/documents/sessions routes (PR #4-#6) NEVER migrated to TanStack Query despite ratification (e) — only shell + ADRs + now Risks on it; recommend dedicated PR; C15a NEW production risk ingest pipeline (parallel to docs C15); C16 + C18 + C20 still open; Cnew-smoke-param + Cnew-test-symmetry (P3/P4 from CoS code-review — non-blocking, defer to PR #10 polish bundle); Cnew tracking-only (least-privilege REVOKE EXECUTE FROM PUBLIC on all new SECURITY DEFINER risk SPs — Steward sister-task). |
PR #11 (merge SHA 3db4982; iter-0 d2e6329 + iter-1a SA TS1 ONE_AUDIT_ROW 45e7bab + iter-1b CoS P2 TanStack migration 1f440b7); SPA via StaticSitesClient.exe direct deployment id 28a9dff5-3409-440f-9fb3-3e3911e4a389 (P10: no --env); Function via az functionapp deployment source config-zip OneDeploy 2fc4e22f-d13d-4b32-ac92-720b47ccc5a1 (P9 + Flex Consumption: zip via [ZipFile]::Open + CreateEntryFromFile preserving dist-bundle/ path; 2 functions registered roles+trpc); 4 DDL audit cross-links (eaa58e2c view + 5e693fa8 start_mitigation + e61effce escalate + 7f3fcd14 assign_owner) + ship audit f4b4ad7a-248a-4d8a-9332-3548a50c53a2. |
2026-06-04 BST |
PR #8 |
ADRs viewer + decision write-back fan-out (Mark decided only / Mark rejected / Assign owner) + ADR-W10-5 ratification + drop --color-* alias-fallback block. Inherits PR #7 chrome+theme. ADRs viewer (§11.6): list route e.$engagement.adrs.index.tsx ordered ratified→reviewed→draft then created_at desc (stage chip on --cp-success/link/warning, owner local-part with full UPN title tooltip per SA Q4-pin, ratified-date column); detail route e.$engagement.adrs.$id.tsx renders body_md via existing react-markdown dep, with empty-state "Body not yet ingested — see repo file at {repoPath}" when null (SA Q1-pin2). ADRs tab in engagement nav flipped from nav-disabled span to live <Link activeProps> (other 9 future tabs stay disabled). 2 new tRPC read procs: read.adrs.list + read.adrs.get; 42P01→[] on list, 42P01→NOT_FOUND on get; no *_record_read audit (C16 deferral preserved). 3 new HS-4L stored procedures (parent-applied DDL pre-CLI per Cardinal Rule #3): sp_decision_mark_decided_only restores §11.5 two-button flow per C7 closure (sets status='decided' WITHOUT minting ADR draft; emits ONE agent_runs row with triggered_by='ui.mark_decided' + payload.promote_skipped=true per SA R3 verb-taxonomy preservation); sp_decision_mark_rejected requires reason_md (NOT NULL, len>0 → P0001 reason_required) with SA R2 legal-transition gate (OLD.status='proposed' → P0001 illegal_transition otherwise), reason persisted to agent_runs.payload.reason_md ONLY (SA R1 — no decisions column add, no ADR-W10-4 trigger); sp_decision_assign_owner writes NEW column decisions.owner_upn ONLY (SA pin P2 — NEVER touches owning_peer which has a CHECK IN ('SA','CoS') CONSTRAINT blocking UPN values, discovered Steward P2 probe), validates against haleon.fn_valid_owners() SECURITY INVOKER text[] whitelist with case-insensitive comparison (SA pin P3) → P0001 invalid_owner otherwise. All 3 SPs SECURITY DEFINER + GRANT EXECUTE w10_app + single-TX + role-guard human:% + xmin OCC + unbounded client_op_id idempotency + alias+qualify UPDATE…RETURNING…INTO per P7-PR #5 ambig-col lesson. 3 new tRPC mutations under mut.decisions: markDecidedOnly / markRejected / assignOwner; identity from ctx.user never input (ADR-W10-1 §3.6); error map stale_row→CONFLICT 409 / hs4l_role_guard→FORBIDDEN 403 / reason_required+illegal_transition+invalid_owner→BAD_REQUEST 400 / 42P01→NOT_FOUND 404. SPA decision-detail fan-out: 3 new buttons gated on status='proposed' for the decided/reject pair, always-visible assign-owner; reject opens drawer with mandatory reason textarea; assign-owner inline form with case-insensitive UPN input (free-text MVP, full people-picker = NEW C20 carry to PR #17); on success queryClient.invalidateQueries(['decisions',engagement,id]). C5c closure: <span data-status="draft-adr"> inert badge AND post-promote banner both flipped to live <Link to="/e/$engagement/adrs/$id">; ADR detail back-link to source decision when linkedDecisionId set; persistent decision.adrId link on detail when set. API DTO discipline (SA pin P1): read.decisions.list/get project ownerUpn + owningPeer as SEPARATE fields (NOT collapsed); legacy owner field DROPPED with SPA sweep to decision.ownerUpn ?? decision.owningPeer in 2 routes (iter-2 SA E1 surgical). ADR-W10-5 ratified (C19 closure): "auth dual-path read-only contract surface" — SA-authored, parent-applied via Steward DDL; back-fills PR #7's tRPC authRouter mount alongside retained SWA Function /api/auth/roles; locks both paths to identical projections of x-ms-client-principal.userRoles; no-mutation hard invariant on both paths enforced by kernel-purity grep (auth.ts has zero .mutation() + new vitest authRouter._def.procedures assertion + Steward audit-cron parity check; future write-back to auth context requires its own ADR. Stored at m-policies/adr/draft-W10-5-20260604.md + haleon.adrs.id=3920aa4b-65bf-4db5-b47a-5470f66d44ff adr_number=13 status='ratified' review_stage='ratified' body_md=13287 chars; ratification audit cceac528-0688-43e9-a160-1e895a2268a8. C17 closure: 13-entry --color-* alias-fallback block dropped from index.css :root; post-drop drift-bomb asserts zero var(--color-*) ANYWHERE in apps/spa/src (was: outside index.css only; now: zero anywhere). Live CSS verified 50 --cp-* refs + ZERO --color-* refs. P7 substrate probes 13/13 PASS (3×decided_only [CONFLICT/FORBIDDEN/IDEM] + 5×rejected [CONFLICT/FORBIDDEN/REASON_REQUIRED/ILLEGAL_TRANSITION/IDEM] + 4×assign_owner [CONFLICT/FORBIDDEN/INVALID_OWNER/IDEM+CASEI] + C1 verb verify triggered_by='ui.mark_decided' per SA R3). 107 API tests green (was 80, +27: 17 decisions.test.ts + 8 new adrs.test.ts + 2 auth.test.ts including shape-parity + no-mutation per ADR-W10-5 §3.5) + 2 SPA tests. ADR-W10-4 not triggered (additive nullable columns only; no agent_runs column change; new HS-4L SPs are additions). Substrate findings (Steward read-only probes): haleon.adrs live cols had no body_md/summary/owner_upn/ratified_at (column drift from design assumption — resolved by adding body_md + reusing accepted_at AS ratifiedAt + reusing decision_id AS linkedDecisionId + reusing accepted_by_email); haleon.decisions.owning_peer CHECK IN ('SA','CoS') blocked UPN reuse → ADD owner_upn text (SA pin P2 reversal). pg_notify('decision_status_change') channel has no live LISTEN consumers (Steward P4 attestation) — mark_decided_only + mark_rejected will fire the pre-existing trigger but no downstream surprise. Carry-forwards: C5c+C7+C17+C19 CLOSED; C16+C18 still open; C20 NEW (Steward audit-cron ADR body_md ↔ repo_path file SHA drift detection) + Cnew tracking-only (consider adding 'ratification' to agent_runs.class_check for finer audit-spine partitioning — tried first, fell back to class='audit' with triggered_by='ratify.adr_w10_5_auth_dual_path' carrying the verb). |
PR #10 (merge SHA ebdf978; iter-0 2ef7bfd + iter-1 97f6c06 + iter-2 c4ca0bd); SPA via StaticSitesClient.exe direct (P10: no --env); Function via az functionapp deployment source config-zip OneDeploy 2f61370d-59fd-4eb1-ac6d-9d6d1352fb5e (P9: zip via [ZipFile]::Open + CreateEntryFromFile preserving dist-bundle/ path; 2 functions registered roles+trpc); audit 9e85cfc0-1176-46d1-a218-c3ab56a5f3c6. |
2026-06-04 BST |
PR #7 |
App shell + theme retrofit + engagement front page + two-TOC nav + TanStack Query + Settings + RefreshedStamp. Foundational chrome inherited by all PR #8–#17. App shell (__root.tsx): sticky header with backdrop-filter:blur(8px) on var(--cp-panel-strong), brand+ring-dot, disabled ⌘K search stub (ships PR #13), Radix DropdownMenu user-menu (Settings + Sign out via /.auth/logout), theme toggle writing data-theme on <html> with localStorage persistence + prefers-color-scheme first-load, skip-link, .container max-width. Theme retrofit: full --cp-* light + html[data-theme="dark"] inversion mirrored in apps/spa/src/index.css + packages/shared/src/design-tokens.ts; --color-* alias-fallback block retained in :root for THIS PR only per ratification (f) (drops PR #8 = C17 carry-forward). Token rename across 15 files / 209 refs; drift-bomb asserts zero var(--color-*) refs outside index.css. Engagement layout route e.$engagement.tsx renders <Outlet/> (P6 trap avoided) + two-row header (name · type · phase + LIVE health pill bound to portfolio.v_engagement_health with --cp-success/warning/danger/text-muted map + RefreshedStamp) + top-tab nav: 6 live tabs (Portfolio · Overview · Decisions · Reports · Documents · Sessions) + 9 disabled tabs as <span role="link" aria-disabled="true" title="Ships in PR #N"> (NEVER <Link>, NEVER <button>, per ratification c + SA-E6). Engagement home e.$engagement.index.tsx (§11.2): markdown OVERVIEW via new read.engagement.overview tRPC proc with repo-standard try/catch on err.code==='42P01' → null (matches reports.ts/decisions.ts, NOT a to_regclass probe per SA-E2) + 3 widget cards (Open Decisions count+top-3, Recent Reports top-5 via byDate(30d), Recent Docs top-5). Empty-state copy rendered when no overview report exists. NO *_record_read audit on nav reads (deferred to N1 = C16 carry-forward, per SA-E3). /settings stub (pinned engagements/theme/UTC placeholders, no 404 from user-menu). TanStack Query: QueryClientProvider + ReactQueryDevtools in __root.tsx; queryClient.staleTime=30000 ADDED preserving existing refetchOnWindowFocus:false + retry:1 (SA-E4); migrated ALL 9 data route components from useState/useEffect/trpcClient to useQuery/useMutation in single squash (no mixed-mode per SA-E2; onError preserves CONFLICT/FORBIDDEN/NOT_FOUND mapping). authRouter mounted on tRPC for user-menu role read (dual-pathway with existing SWA Function /api/auth/roles; mutation-free per SA-A1; ADR-W10-5 follow-up draft = C19). RefreshedStamp auto-ticks every 10s. Test setup resets <html data-theme> between vitest runs (SA-E3). 82 tests green (80 API +2 SPA; +4 new engagement.overview covering mapped row / 0 rows / 42P01 / re-throw on non-42P01). ADR-W10-4 not triggered (no agent_runs column or view shape change). Substrate findings: portfolio.v_engagement_health EXISTS (currently band=unknown for both seeded engagements); haleon.reports does NOT exist (42P01 path validated). Carry-forwards: C16 N1 canonical *_record_read consolidation pre nav-audit, C17 drop --color-* aliases PR #8, C18 bind user-menu UPN from auth context (defer PR #17), C19 ADR-W10-5 (auth dual-path read-only contract surface; SA owns draft pre-PR #8 ship). |
2026-06-04 ~05:10 BST |
shipped |
PR #9 · commit 802597f · deploy 8d07e39a / 181fcdca |
PR #6 |
W10 MVP closeout. Documents viewer (Sub-PR-A): list + detail SPA routes (/e/$engagement/documents, /e/$engagement/documents/$id) over the PR #2.5a read-canon scaffold; dispatchByBackend wired for storage_backend='blob' via @azure/storage-blob + DefaultAzureCredential (UAMI id-w10-api-uks) reading from stw10apiuks/documents/<storage_path> per ADR-W10-3 §3.4; sharepoint/external_url/ado_attachment backends stubbed not_in_mvp (C12/C13 W11). 403-on-deny audit (SA-E2): new SP sp_document_record_read_denied(role, doc_id, claimed_label) applied SECURITY DEFINER (audit aab7f424) and called in requireSensitivityClearance catch path BEFORE rethrowing FORBIDDEN; last_seen_at UPDATE constrained to blob-success branch only (SA-E3); consumedAt projection derived from agent_runs audit join (payload key pk); path-injection zod refinement on storage_path before BlobClient compose. Pre-CLI fixture document id=1 (W10 MVP Welcome, Internal, audit da805686). Sessions browser (Sub-PR-B): SPA-only routes (/e/$engagement/sessions, /e/$engagement/sessions/$id) rendering plan.md via react-markdown + files/ tree depth-2 default; data fetched client-side from corpus-mirror SWA jolly-dune via sessions/index.json manifest (no API proxy per ratification (c)); zero new apps/api code (SA-E4 dropped sp_session_record_read entirely — SWA edge logs are the access record); engagement-tag inline in manifest (SA-E6 fallback, SWA-managed mirror has no x-ms-meta-* per Steward attestation). Session-state implicit-Internal class (SA-E5) — never Confidential-Customer/Regulated-Restricted. Corpus-sync extension (Sub-PR-C): operator-side sync.ps1 + SKILL.md updated for new sessions container with plan.md+files/-only privacy filter (RISK-1) excluding checkpoints//turn logs/state JSON; CORS staticwebapp.config.json committed at infra/corpus-mirror-staticwebapp.config.json (FLAG-7). Tests: 78 passing (73 + 5 new A1–A5 covering blob branch, denied-audit, last_seen_at scope). |
2026-06-03 ~20:30 BST |
shipped |
PR #8 · commit 14315a4 · deploy ae19b59f / 4c98d23e |
PR #5 |
Decisions: mark-decided + promote-to-ADR-draft (second human write-back; first optimistic-concurrency surface). Single modal on the decision detail (clientOpId at modal-open, react-markdown body + adrSummary preview, inert <span data-status="draft-adr"> badge — no anchor, ADR viewer ships W11). New tRPC mut.decisions.markDecided (protectedProcedure, identity ctx-derived) invokes live HS-4L SP sp_decision_mark_decided — one SP body / one TX, role-guarded (human:%), xmin optimistic concurrency (stale → P0001 stale_row → CONFLICT 409 → toast + re-fetch), unbounded client_op_id idempotency, emitting TWO agent_runs rows per ADR-W10-1 §3.2 verb table (ui.mark_decided on the decision + ui.promote_adr_draft on the new adrs draft, shared client_op_id). Atomic flip status='decided' + mint adrs draft (review_stage='draft') in the single TX. read.decisions.history widened to surface all three verbs. §11.5 single-button MVP (save-decided-only variant = W11 carry-forward C7). |
2026-06-03 ~19:35 BST |
shipped |
PR #7 · commit 62f5641 · deploy dbaaeb0d |
PR #4 |
Decisions: list + detail + first human write-back (addNote). 2 SPA routes (/e/:engagement/decisions list w/ status+class filters + deep-link query state, /e/:engagement/decisions/:id detail + optimistic note drawer) via TanStack file-based routing. 4 tRPC procs (read.decisions.list/get/history + mut.decisions.addNote) all protectedProcedure; identity ctx-derived from x-ms-client-principal (never a tRPC input). addNote invokes the live HS-4L SP sp_decision_add_note — single-TX, role-guarded (human:% or hs4l_role_guard P0001), unbounded client_op_id idempotency, widened to RETURNS TABLE(run_id, note_id, body_md, created_at, actor_role). “Human activity” panel consumes haleon.v_human_touchpoints (human-only). Mandatory front-door nav: non-nested “Decisions →” <Link> sibling on every EngagementCard (browser-smoke-gated, no stub). Graceful 42P01→NOT_FOUND for the test_engagement_1 substrate gap (te1 SP mirror = follow-up Steward task, parallel to PR #5). xmin::text projected in list+get to forward-bind PR #5 optimistic concurrency. |
2026-06-03 ~17:04 BST |
shipped |
PR #6 · commit e6ce78a · deploy b78871b8 |
PR #3 |
Reports list + viewer (read-only). 3 SPA routes (/, /e/:engagement/reports with By Date | By Type tabs, /e/:engagement/reports/:type/:id viewer) wired via TanStack file-based routing. 3 tRPC procs (read.reports.byDate/byType/get) all protectedProcedure, audit-emitting via sp_record_read (prefix-normalized at this PR). 7 typed handlers (handler-map dispatch — no DB-stored SQL execution); asOfDate-DESC sort with range chips; graceful 42P01 fallback for the 5 test_engagement_1 substrate-gapped slugs. Shared resolveSchema helper extracted. MVP scope ratified: 7 aggregates per engagement; per-instance enumeration deferred (see docs/pr-3-README.md). |
2026-06-03 ~11:40 BST |
shipped |
PR #4 · commit 46c80bb · deploy 4db7e4b1 |
PR #2.5a |
Design-canon read core. read.documents.list/body (sensitivity-gated per ADR-W10-3, audit-emitting via new combined SP sp_document_record_read) + mut.documents.markConsumed (HS-4L, client_op_id idempotent) + in-repo m_read_canon shim. Identity from x-ms-client-principal → ctx.user + protectedProcedure; engagement → schema_name resolved via portfolio.engagements_registry + regex allow-list (no raw SQL interpolation). Pack schema-v3: dedicated _w10.pack.schema.json minted; _w10/pack.json bumped to v3 with populated canon block + both seed engagements. m_read_canon staged optional in 4 role overlays (SA/CoS/Steward/LibraryCurator); flip to required gated on FR-002 host m-tools surfacing. test_engagement_1 schema mirror back-filled to match haleon canon surface. |
2026-06-03 ~10:02 BST |
shipped |
PR #3 · commit 9b8ec0c · deploy f2b32bfa |
PR #2 |
Engagements registry + portfolio overview (read-only). First real DB connection via UAMI AAD token to PG Flex (w10_app role NOINHERIT, SET ROLE on connect). Folds PR #1 carry-forwards: auth claim-shape hardening, single auth pathway, in-DB Bicep comment block. |
2026-06-02 ~18:25 BST |
shipped |
PR #2 · live API |
PR #1 |
“Hello W10” deploy proof — SPA shell + Function health.ping/auth.roles + SWA Entra auth config + Bicep. Proves the deploy + auth path end-to-end. |
2026-06-02 ~16:00 BST |
shipped |
PR #1 · live |