feat: add deterministic shared triage foundation - #4
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Land only the bounded shared executable foundation that lets the later Evidence Desk and Operations streams work in parallel: framework-free @huddle/application ports and DTOs; per-fired-rule signal/evidence identity and exact additional evidence; server-only GuideAccess composition; ordered DB/migration seams; PostgreSQL-driver isolation; truthful synthetic-only legacy/import policies; half-open America/Chicago boundaries; separate response semantics from timing profiles; centralized fingerprinted synthetic-demo severity; and the captain-approved portfolio regression/no-accuracy-claim specification. Do not build UI, import/refresh pipelines, model runtime, deployment, reset, README packaging, or the full simulator/eval. Preserve deterministic, grounded, synthetic-only boundaries.
What Changed
Risk Assessment
✅ Low: The follow-up consistently closes the composite activity-identity and civil-date gaps without expanding scope, and the full source review found no remaining material issue.
Testing
After base-to-target intent inspection, the focused automated tests and runtime evidence probe passed, directly demonstrating the shared foundation’s deterministic, grounded, synthetic-only boundaries. No screenshot was captured because this change intentionally contains no UI; the reviewer-visible artifact is the actual developer-facing runtime JSON.
Evidence: Shared foundation runtime evidence
Runtime JSON demonstrates 21 acceptance assertions, including per-rule evidence identity, DST-safe half-open windows, synthetic-only policies, timing-profile separation, server/driver boundaries, migration ordering, and portfolio no-accuracy-claim policy.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 7 issues found → auto-fixed (3) ✅
db/migrations/007_shared_foundation.sql:17- The required “truthful synthetic-only legacy/import policies” containment remains bypassable. Existing rows are marked legacy, but the new column defaults to false; the unchanged legacy nightly writer omits this column, so its newly inserted provenance-free entries passlegacy_non_displayable IS FALSEand become visible. Default legacy rows to non-displayable and let only the future run-backed compiler explicitly publish displayable entries.packages/signal-engine/src/windows.ts:67- The required “half-open America/Chicago boundaries” do not hold end-to-end.boardWindowsreturns Chicago midnight, butscripts/nightly.tsstill uses UTC end-of-day as the mastery and engine end anchor; rules also query mastery atctx.nowinstead ofwindow.end. For a 2026-03-09 board this can include Monday daytime activity belonging to the next period. Use one canonicalwindowEnd/asOffor every decision read and reservenowfor provenance.packages/signal-engine/src/engine.ts:220- The required “per-fired-rule signal/evidence identity and exact additional evidence” is incomplete because conflicts are calculated inside each skill or cross-skill evaluation before the complete student signal set exists. A skill-scoped cause and an overlapping cross-skill cause therefore retainconflictMultiplier: 1and omit each other from conflicts; additionally, the locally recomputedadditionalCauses.finalConfidenceomits the linked signal's conflict multiplier. Reconcile all student signals and final confidences before assembling bundles, fingerprints, and ranking disclosures.packages/signal-engine/src/engine.ts:244- Each signal's behavior fingerprint hashes only the active unit subset passed toevaluateContext. Per-skill and cross-skill signals consequently receive different fingerprints, and changing a cross-skill rule leaves every per-skill fingerprint unchanged even though that rule can change board dominance. Compute one fingerprint from the complete rule set atrunEngineand pass it into every evaluation.packages/signal-engine/src/engine.ts:315-evidenceFingerprintis assigned the raw, non-canonical JSON bundle rather than a lowercase SHA-256 fingerprint. This cannot satisfy the planned 64-hex persistence constraint or provide stable finding identity. Hash canonical evidence at the shared identity boundary, excluding persistence-only surrogate IDs as specified by the data model.db/migrations/007_shared_foundation.sql:11- The required separation of response semantics from timing profiles is lost during migration: every existing item receivesstandard_multiple_choice, including the four seededwp:items whose canonical timing profile isword_problem. Later ingest will apply the 180-second bound instead of 300 seconds. Backfill timing profiles from the canonical seeded mapping before treating the column as authoritative.packages/signal-engine/src/confidence.ts:27- The new confidence breakdown does not implement the frozen rule-requirement matrix. It derives timing attenuation solely from attempt quality, so timing-free answer-choice or engagement rules are reduced to 0.45 onnonedata instead of remaining at 1.0; it also compounds a 0.75 conflict penalty by family count although the contract specifies one 0.70 penalty. Because final confidence is now a ranking tiebreaker, this changes board order. Calculate multipliers from the rule's declared timing requirement and the one-time conflict policy.🔧 Fix: Fix deterministic signal boundaries and legacy containment
5 errors still open:
packages/signal-engine/src/engine.ts:224- The required “per-fired-rule signal/evidence identity” still records the config-derived version for every signal instead of the fired contract’srecord.rule.version. Changing a custom rule from v1 to v2 changes the behavior fingerprint while its signal and evidence continue reporting the same local rule version. Persist the fired rule’s version and reserve the behavior fingerprint for whole-policy identity.packages/signal-engine/src/evidence.ts:38- The durable identity/exact-evidence fix still uses database surrogate IDs as semantic keys before hashing. Null IDs collapse to0, which can include unrelated attempts and create false cross-skill conflicts; tied timestamps are ordered by ID before ordinals and derived values are computed, so rekeying can still change the fingerprint after IDs are stripped. Carry a stable source-event identity through rule evidence, overlap checks, and bundle ordering.packages/signal-engine/src/evidence.ts:66- The required “separate response semantics from timing profiles” is not preserved end-to-end. The engine dropsitemTypeandtimingProfile, evidence hardcodes every attempt asmultiple_choice, andwinsorizesilently defaults an omitted profile tostandard_multiple_choice; numeric/short-text evidence is therefore false and omitted word-problem profiles receive the 180-second bound. Carry resolved item metadata through the shared context and require the timing profile at winsorization.packages/signal-engine/src/engine.ts:485- The required half-open America/Chicago invariant still fails for attendance. Date-only spans are interpreted in the process timezone and extended by a fixed 24 hours; against Chicago windows, a July 21–23 absence parsed at UTC midnight overlaps 67 rather than 72 hours, and DST creates another mismatch. Normalize inclusive attendance dates to Chicago[start, end+1)civil boundaries and prorate using civil days.specs/001-huddle-triage-board/spec.md:753- The changed captain-approved portfolio gate conflicts withcontracts/eval-harness.md, which still says external synthetic-demo promotion before the complete eval bundle is unresolved. This leaves later streams with contradictory release criteria. Align that contract with the bounded grounding/fallback-regression and no-accuracy-claim policy while retaining the full eval requirement for pilots or accuracy-backed claims.🔧 Fix: Align evidence identity, metadata, calendar, and portfolio gates
2 errors still open:
packages/signal-engine/src/contract.ts:48- The approved “per-fired-rule signal/evidence identity and exact additional evidence” correction still keys evidence only bysourceEventId, while both the current schema and target ingest contract define event identity usingsourceas well. For one student withsource-a/event-1andsource-b/event-1, a rule citing either ID makes bundle selection and cross-skill overlap match both attempts. Create an opaque composite activity identity at the persistence/compiler boundary and use it throughout without exposing the raw source.packages/signal-engine/src/windows.ts:54- The approved “half-open America/Chicago boundaries” fix remains process-timezone dependent. PostgreSQLdatevalues are loaded as process-localDateobjects, butchicagoDateOnlyBoundaryextracts their UTC date withtoISOString(); in an east-of-UTC process, a stored2026-03-08becomes2026-03-07before the Chicago boundary is calculated. Preserve absence dates asYYYY-MM-DDcivil strings from the database/domain boundary and convert those directly.🔧 Fix: Harden activity identity and civil-date boundaries
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
Inspectedgit diff --name-status 5d25aba567c4fd66d5aa6dd5856984e9b2946969..61253dbf7f83586ef5a89876423b8eb9d8ca5b89against the authoritative intent, plan, and constitution.Ran the targeted 17-file Vitest command covering application contracts, GuideAccess, DB seams and isolation, activity identity, ingest timing, seed policy, narration requests, evidence identity, Chicago boundaries, confidence, ranking, and determinism.Rannpm exec tsx -- /var/folders/nt/rdk7cjs538l8zphln24q2k900000gn/T/no-mistakes-evidence/01KYQDPZ9FGPFA49HHJ69KP6EC/shared-foundation-probe.ts.Verified all 21 assertions inshared-foundation-runtime.json, recorded SHA-256a12dba7e3a9a9045056d307e74ed5f6fb1687ca0bebf4d34c36a13269ba0004b, and confirmedgit status --shortremained clean.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.