Audit — P4.3.1 Three Advisory Roles (Translator / Sentinel / Guide)
1. Task framing
P4.3.1 ships the role adapter layer of the μ Integrity Monitor. It sits between P4.1.1 (the advisory envelope produced by detectors) and P4.6.1 (MCP tool surface that exposes role outputs). It is a pure presentation layer: roles consume advisories and produce three distinct output surfaces:
- Translator → human-readable summary string
- Sentinel → severity-gated escalation flag (
SentinelFlag | null) - Guide → grouped corrective-action suggestions (
Suggestion[])
The hard invariant is read-only: no role may mutate state, call a database, invoke π/α/κ/λ directly, or emit new advisories. Roles consume what detectors emit; they do not detect.
2. Source-of-truth references
| Source | Location | Content |
|---|---|---|
| Concept doc | docs/3-world/physics/enforcement/integrity.md §Three advisory roles (L117-127) |
The three-roles table + read-only invariant + “no mutator role” rule |
| Spec law | docs/spec/s14-integrity-monitor.md §Advisory roles |
Translator/Sentinel/Guide access matrix (read input / read queue / read state) |
| Task prompt | docs/guides/implementation/task-prompts/p4.1-mu-integrity.md §P4.3.1 (L848-1032) |
Detailed acceptance criteria + ready-to-paste prompt |
| Envelope | src/domains/integrity/schema.ts |
Advisory, AdvisorySchema, AdvisorySeveritySchema, the 8 fields |
2.1. Quotation — integrity.md L117-127
Three advisory roles
μ’s output is consumed by three roles, each with a different authority level:
Role Authority Response Translator read-only Summarize advisory reports for a human operator; no recommendations of its own Sentinel read-only Flag advisory reports that meet a severity threshold; may escalate to π Guide read-only Suggest corrective actions for human review; the human decides whether to act All three are read-only. μ does not have a “mutator” role. An advisory report that prescribes action is still a report; execution requires a separate π governance proposal or a T0-human authorization.
2.2. Quotation — s14-integrity-monitor.md
Advisory roles
Role Access Can execute Translator Read input Suggest commands, cannot execute Sentinel Read event queue + rule set Detect patterns, cannot veto Guide Read state Explain state, cannot modify
The s14 phrasing is older (donor-era); the canonical contract is integrity.md
L117-127 per R91 audit hierarchy. P4.3.1 honors both descriptions: every role
is strictly read-only and the Sentinel emits a flag rather than calling
π directly (so it satisfies both “may escalate to π” and “cannot veto”).
3. Envelope surface inventory (P4.1.1)
The fields the roles consume from Advisory:
| Field | Type | Role consumption |
|---|---|---|
role |
'Translator' \| 'Sentinel' \| 'Guide' |
The advisory’s original role tag from the detector (NOT the consuming role); roles do not re-tag |
check |
'circular_logic' \| 'coercion_trap' \| 'axiom_drift' \| 'axiom_regression' |
Translator surfaces, Sentinel filters, Guide groups by |
result |
'PASS' \| 'WARN' \| 'BLOCK' |
Translator surfaces |
severity |
'LOW' \| 'MED' \| 'HIGH' |
Sentinel gates by threshold |
evidence |
unknown[] |
Translator may surface count; Guide cites in advisory_refs indirectly via decision_hash |
recommendation |
string |
Translator surfaces verbatim; Guide may aggregate into rationale |
decision_hash |
64-char lowercase hex | Guide uses as advisory_refs[] cite key |
timestamp_logical |
non-negative bigint | Reserved for future use; not surfaced in v1 outputs |
4. Output shapes — survey of similar Phase 0/1 patterns
The Phase 0 ζ Decision Trail surfaces (thought_record, thought_record_list)
emit human-readable summaries via summary columns; the κ rule-engine
verifies via recommendation strings; the λ Reputation surface returns typed
JSON envelopes. P4.3.1 follows the typed-envelope pattern:
// Sentinel flag — analog to λ reputation_check_gates return shape
type SentinelFlag = {
action: 'escalate_to_pi' | 'log_only';
reason: string;
advisory: Advisory;
};
// Guide suggestion — analog to κ rule-engine validation result groupings
type Suggestion = {
headline: string;
advisory_refs: string[]; // decision_hash values (64-char hex)
rationale: string;
};
Translator emits a string directly — the simplest possible presentation
surface, suitable for log lines, MCP content[].text payloads, or operator
console rendering.
5. Forbidden surfaces (the “no mutation” perimeter)
The static-grep gate (acceptance criterion + CI grep) forbids the following
substrings in roles.ts:
| Pattern | Reason |
|---|---|
db.run |
better-sqlite3 mutation API |
db.exec |
better-sqlite3 multi-statement mutation |
db.prepare |
better-sqlite3 statement compile (could prepare an INSERT/UPDATE) |
UPDATE |
SQL keyword (case-insensitive match in test) |
INSERT |
SQL keyword |
DELETE |
SQL keyword |
update[A-Z] |
TypeScript verb naming (e.g. updateRow, updateState) |
mutate |
TypeScript verb naming |
Date.now() |
Wall-clock read (non-deterministic) |
Math.random() |
Randomness (non-deterministic) |
performance.now |
Wall-clock read |
Roles may call into pure helpers from ./schema.js (e.g. computeDecisionHash
is a pure SHA-256), but may NOT import:
../tasks/repository.js(mutates the task store)../reputation/repository.js(mutates λ ledgers)../consensus/repository.js(mutates θ vote logs)../trail/repository.js(mutates ζ thought records)../proof/repository.js(mutates η Merkle leaves)node:fs,node:crypto,better-sqlite3
Pure deterministic imports allowed: ./schema.js types only.
6. Severity-rank semantics (Sentinel gating)
The severity-rank mapping for the Sentinel threshold gate:
| Severity | Rank | Notes |
|---|---|---|
LOW |
0 | λ-aligned per R91 audit Q6 |
MED |
1 | |
HIGH |
2 |
The gate is inclusive at the boundary: advisory.severity rank >= threshold rank
returns a flag. Examples:
| Advisory severity | Threshold | Flag returned? |
|---|---|---|
HIGH |
LOW |
yes (2 ≥ 0) |
HIGH |
MED |
yes (2 ≥ 1) |
HIGH |
HIGH |
yes (2 ≥ 2) |
MED |
LOW |
yes (1 ≥ 0) |
MED |
MED |
yes (1 ≥ 1) |
MED |
HIGH |
no (1 < 2) |
LOW |
LOW |
yes (0 ≥ 0) |
LOW |
MED |
no (0 < 1) |
LOW |
HIGH |
no (0 < 2) |
Flag action — always 'escalate_to_pi' when the flag fires (per
integrity.md L124: “may escalate to π”). The 'log_only' action is reserved
for downstream use (e.g. P4.4.1 escalation FSM may emit it for result: 'PASS'
advisories that pass the threshold). In P4.3.1 the Sentinel always emits
escalate_to_pi-tagged flags when the gate fires; the 'log_only' literal is
exported in the union for forward compatibility but not produced by
Sentinel.flag in P4.3.1 — it stays available for P4.4.1’s mapping table.
7. Guide grouping semantics
Guide.suggest(state, advisories) groups by advisory.check (4 possible
values per AdvisoryCheckSchema). The output contract:
- Cardinality — at most 4
Suggestions (one per distinctcheck). - Headline — derived from the check value (e.g.
'Address circular logic','Address coercion trap','Address axiom drift','Address axiom regression'). - advisory_refs — array of
decision_hashvalues from grouped advisories, preserving input order (deterministic). - rationale — composed from the count + grouped advisories’ recommendations.
- state parameter — currently unused by the v1 grouping logic; reserved for
future extension (e.g. system-state-aware suggestion ranking). Typed as
unknownto keep the call site stable.
Empty input → empty array. Single advisory → 1 Suggestion of size 1.
8. Module layout
| Path | Role | Note |
|---|---|---|
src/domains/integrity/roles.ts |
Implementation | Pure module; no I/O, no async |
src/__tests__/domains/integrity/roles.test.ts |
Tests | Mirrors detector test layout |
Test layout matches sibling Wave-2 tests (e.g. src/__tests__/domains/integrity/detectors/drift.test.ts).
The roles tests live one directory above detectors/ since roles.ts is at
src/domains/integrity/roles.ts (sibling of detectors/, not nested).
9. Acceptance-criteria mapping
Each AC from the task prompt traces to a section in the contract (§7):
| AC | Source | Contract § |
|---|---|---|
AC#1 Translator class with readonly role |
integrity.md L123 |
§3.1 |
AC#2 Sentinel class with readonly role |
integrity.md L124 |
§3.2 |
AC#3 Guide class with readonly role |
integrity.md L125 |
§3.3 |
| AC#4 Output types distinct (string / SentinelFlag|null / Suggestion[]) | task prompt | §3 |
| AC#5 No “Mutator” role exported | integrity.md L127 |
§I1 |
| AC#6 Sentinel emits flag, never calls π | integrity.md L124 |
§3.2 + §I2 |
| AC#7 Guide suggests, never executes | integrity.md L125 |
§3.3 + §I2 |
| AC#8 Static-analysis gate passes (no mutation tokens) | task prompt | §I3 |
AC#9 npm run build && npm run lint && npm test pass |
task prompt | §8 |
AC#10 No Date.now(), Math.random(), performance.now() |
task prompt | §I4 |
| AC#11 Severity-rank semantics (inclusive boundary) | this audit §6 | §3.2.2 |
| AC#12 Guide cardinality ≤ 4 (one per check) | this audit §7 | §3.3.2 |
10. Open questions resolved
| Q | Resolution |
|---|---|
Should Sentinel ever emit action: 'log_only'? |
No in P4.3.1 — the literal is exported for forward compat (P4.4.1 may use it); Sentinel.flag only ever emits 'escalate_to_pi' or null. |
Should Guide consume the state parameter? |
No in v1 — reserved for future extension. Tests verify state is ignored (passing two different state values produces identical output). |
| Are role classes singletons? | No — they are stateless adapters. Multiple instances are allowed (no class-level mutable state). |
| Does Translator output need to be parseable? | No — it is human-facing. Tests check substring containment, not structure. |
Does Sentinel need to set advisory on the flag, or just the hash? |
Yes — full advisory by reference. Cheap; the flag is short-lived. |
Does Guide preserve input order in advisory_refs? |
Yes — for determinism (mirrors κ canonical iteration order). |
11. Risk surface
- Cargo-cult readonly: TypeScript
readonlyis compile-time only. The runtime guard is the CI grep gate (§5). The test file MUST include a static scanner that readsroles.tsand asserts none of the forbidden patterns appear. Without this test, thereadonlymodifier is decorative. - Severity ordering must match λ (R91 audit Q6 alignment). The mapping
LOW=0, MED=1, HIGH=2is canonical across μ + λ; do NOT introduce a μ-local ordering. - The advisory
rolefield is informational, not behavioral. A detector emits anAdvisorywithrole: 'Sentinel'(or similar); the consumingSentineladapter does NOT check this field. The role tag on the advisory encodes the origin (which detector layer produced it); the consuming role encodes the destination (how it is presented).
12. Out of scope
- Persistence —
mcp_advisoriestable lands in P4.5.1. - Escalation FSM — 4-result + 3-invariant mapping lands in P4.4.1.
- MCP tool surface —
mu_*tools land in P4.6.1. - Fork-hook subscriber — π fork-event integration lands in P4.8.1.
- Parity harness — 4-mock parity test lands in P4.7.1.
P4.3.1 is the pure presentation layer; everything downstream consumes
Translator.summarize, Sentinel.flag, and Guide.suggest outputs.
End of audit.