PIDX Readiness Logic v0.1 — ref normalization, status precedence, warning flags, anti-false-green
PIDX Readiness Logic v0.1 — normalization, precedence, warnings, anti-false-green
Path:
knowledge/dev/laws-new/workflow-manage/design/pidx-readiness-logic-v0.1.mdStatus: DESIGN · v0.1 · NON-AUTHORIZING · 0 PG objects. Specifies the computed semantics ofv_pidx_procedure_readiness. Date: 2026-06-23 Companions:pidx-build-design-v0.1.md(architecture/grammar freeze),pidx-ddl-candidate-v0.1.sql.md(candidate SQL),pidx-test-plan-v0.1.md(validation). Truth rule (non-negotiable): Only PG/SQL-derived existence can setREADY. A declaration, manifest, note, RAG result, or seed status can route and suggest but can NEVER setREADY.
1. Resolver contract
A single side-effect-free resolver maps (ingredient_kind, ingredient_ref, ref_status) → computed_status using ONLY the probes frozen in §2. Properties:
- Deterministic & pure — read-only
SELECT/EXISTS, no writes, no side effects. - Bounded / narrow — resolves one ref at a time; the hot path resolves only the handful of refs belonging to ONE queried procedure. Never scans the whole catalog to answer one question.
- Honest — returns exactly one of
EXISTS · MISSING · UNKNOWN_SOURCE · INVALID_REF · READ_BLOCKED(+STALEdowngrade when the inventory branch reportsrefresh_required). - Grammar-locked — both
pidx_procedure_ingredient.ingredient_refandv_pidx_inventory_current.object_refimport the SAME grammar; neither side hand-rolls a different parse.
2. Ref normalization per kind (write-side + probe-side must agree)
For each kind: canonical format · normalization rule · case · schema-default · PG probe · UNKNOWN_SOURCE fallback · valid · invalid · warning.
CATALOG class (lowercase, schema-defaulted to public, catalog-proven, always fresh)
| kind | canonical | normalize | case | schema | PG probe | valid | invalid | warning |
|---|---|---|---|---|---|---|---|---|
collection |
collection:<schema>.<table> |
lowercase; add public. if schema omitted; store resolved |
fold lower | default public |
information_schema.tables ∪ directus_collections |
collection:public.dot_tools |
collection:dot_tools (un-normalized → must store public.dot_tools) |
LOGICAL_PHYSICAL_MISMATCH |
view |
view:<schema>.<view> |
lowercase; default schema | fold lower | public |
information_schema.views + pg_class relkind in ('v','m') |
view:public.v_pidx_procedure_readiness |
view:public.dot_tools (it is a table) |
— |
field |
field:<schema>.<table>.<column> |
lowercase; default schema | fold lower | public |
information_schema.columns |
field:public.dot_tools.code |
field:public.dot_tools (no column part) → INVALID_REF |
— |
trigger |
trigger:<schema>.<table>.<name> |
lowercase; default schema | fold lower | public |
information_schema.triggers |
trigger:public.birth_registry.trg_birth_gate |
trigger:trg_birth_gate (no table) → INVALID_REF |
— |
function |
function:<schema>.<name> |
lowercase; default schema; name-only | fold lower | public |
pg_proc ⋈ pg_namespace |
function:public.fn_birth_gate |
function:fn_birth_gate (no schema → defaults public, OK) |
OVERLOADED_FUNCTION (name count > 1) |
CODE class (case-SENSITIVE, no schema, registry-proven)
| kind | canonical | normalize | case | ns | PG probe | valid | invalid | fallback |
|---|---|---|---|---|---|---|---|---|
dot |
dot:<CODE> |
store exact SSOT case | sensitive | none | dot_tools.code |
dot:DOT_KG_EXPLAIN, dot:DOT-063 |
dot:dot_kg_explain (wrong case → real MISSING) |
MISSING |
approval |
approval:<action_code> |
store exact | sensitive | none | apr_action_types.action_code |
approval:patch_ops_code |
approval:PatchOpsCode |
MISSING |
event |
event:<domain>.<type> |
store exact; domain ns required | sensitive | domain | event_type_registry(event_domain,event_type) |
event:<domain>.<type> |
event:sometype (no domain) → INVALID_REF |
MISSING |
procedure |
procedure:<code> |
store exact; probe pidx_procedure then workflows |
sensitive | none | pidx_procedure.procedure_code → workflows.process_code |
procedure:PROC_CREATE_NEW_DOT, procedure:WF-001 |
— | MISSING |
label |
label:<code> or label:<facet>.<code> |
store exact; prefer facet-qualified | sensitive | optional facet | taxonomy.code (facet-scoped) |
label:export.priority |
bare code matching many facets | AMBIGUOUS_LABEL → metadata, else MISSING |
io |
io:<code> |
— | sensitive | none | only dot_agent_api_contract.dot_code (narrow) |
— | — | UNKNOWN_SOURCE (no general IO model) |
checker |
checker:<code> |
— | sensitive | none | no SSOT (scattered) | — | — | UNKNOWN_SOURCE |
template |
template:<code> |
— | sensitive | none | unconfirmed (NEEDS_OWNER_DECISION) | — | — | UNKNOWN_SOURCE |
report |
report:<code> |
— | sensitive | none | KB/agent-data (external to PG) | — | — | UNKNOWN_SOURCE |
MISSINGvsUNKNOWN_SOURCEis load-bearing.MISSING= "we know the source and it isn't there → go create it."UNKNOWN_SOURCE= "we have no clean PG source to check → triage the source; do NOT fabricate a green." The four UNKNOWN_SOURCE kinds (io/checker/template/report) never resolve toMISSING.
3. Per-ingredient computed_status precedence (top-down, first match wins)
1. ref_status = 'UNNORMALIZED' OR structurally unparseable (no ':' / empty identifier) -> INVALID_REF
2. kind ∈ {io, checker, template, report} -> UNKNOWN_SOURCE
3. CATALOG kind AND parsed schema ∈ {cutter_governance, sandbox_tac} (read-denied) -> READ_BLOCKED
4. ref resolves AND object found -> EXISTS
(downgrade to STALE iff the inventory branch reports freshness_status='refresh_required')
5. ref resolves AND object NOT found -> MISSING
Emitted per-ingredient statuses: EXISTS · MISSING · UNKNOWN_SOURCE · INVALID_REF · READ_BLOCKED · STALE.
Note: a CATALOG kind missing a required identifier part (e.g.
field:public.dot_toolswith no column) is caught by rule 1 (structurally unparseable for that kind) →INVALID_REF, not a silentMISSING.
4. Per-procedure computed_readiness rollup (precedence)
The gating set = ingredients with required_level = 'required'. optional / nice_to_have / UNKNOWN / NEEDS_TRIAGE are non-gating (they warn, they do not block).
zero ingredient rows -> UNMAPPED
any REQUIRED ingredient in {INVALID_REF, READ_BLOCKED} -> NOT_READY (+ data-quality)
any REQUIRED ingredient in {MISSING, UNKNOWN_SOURCE} -> NOT_READY
all REQUIRED ingredients EXISTS, but ANY of:
- a warning flag present on any ingredient (OVERLOADED_FUNCTION / AMBIGUOUS_LABEL /
LOGICAL_PHYSICAL_MISMATCH / APPROVAL_HANDLER_UNIMPLEMENTED / STALE_SOURCE), OR
- any REQUIRED ingredient is STALE, OR
- any NON-GATING ingredient is MISSING / UNKNOWN_SOURCE / INVALID_REF / READ_BLOCKED, OR
- any required_level ∈ {UNKNOWN, NEEDS_TRIAGE} -> READY_WITH_WARNINGS
otherwise (all REQUIRED EXISTS, no warnings) -> READY
Per-procedure statuses: UNMAPPED · NOT_READY · READY_WITH_WARNINGS · READY.
4.1 READINESS_DRIFT (orthogonal flag, not a status value)
declared_maturity ∈ {checklist_ready, dot_sequence_ready, one_button_ready}
AND computed_readiness ∈ {NOT_READY, UNMAPPED}
-> READINESS_DRIFT = true
A procedure whose note/maturity claims it is ready, but whose required dot:/collection:/procedure: ingredients are MISSING/UNKNOWN_SOURCE, computes NOT_READY and raises READINESS_DRIFT. READY_WITH_WARNINGS does not raise hard drift — its warning_flags already surface the gap.
Full status set emitted by the readiness layer (matches macro §4 Task 5):
EXISTS, MISSING, UNKNOWN_SOURCE, UNMAPPED, INVALID_REF, READ_BLOCKED, STALE, READY, READY_WITH_WARNINGS, NOT_READY, READINESS_DRIFT.
5. Warning flags catalog
| flag | raised when | grain |
|---|---|---|
OVERLOADED_FUNCTION |
function: name resolves to > 1 proc in the schema (signature not checked in v0.1) |
ingredient |
AMBIGUOUS_LABEL |
bare label:<code> (no facet) matches > 1 facet in taxonomy |
ingredient |
LOGICAL_PHYSICAL_MISMATCH |
collection: exists as physical table XOR logical Directus collection (not both) |
ingredient |
APPROVAL_HANDLER_UNIMPLEMENTED |
approval:<code> EXISTS in apr_action_types but handler_ref is null/'unimplemented' (11 of 14 today) |
ingredient |
STALE_SOURCE |
a registry-cache-derived probe reports freshness_status='refresh_required' (rare in v0.1 pure views) |
ingredient |
UNKNOWN_SOURCE_ACCEPTED |
a NON-GATING ingredient resolved UNKNOWN_SOURCE and was accepted without blocking |
procedure |
Top-level warning_flags on a procedure row = the distinct union of its ingredients' flags (+ READINESS_DRIFT carried alongside).
6. Kind-specific rules (explicit)
- Approval.
approval:<action_code>resolvesEXISTSifapr_action_typescontains the action. If itshandler_refis null/unimplemented, it stillEXISTS(the gate is declared) but raisesAPPROVAL_HANDLER_UNIMPLEMENTED→ the procedure becomesREADY_WITH_WARNINGS, never silentREADY. (Live: onlypatch_ops_code,create_item,update_item,add_fieldare implemented; the other 11 areunimplemented.) - Function.
function:schema.nameis name-only existence in v0.1. If the name is overloaded (count > 1),EXISTS+OVERLOADED_FUNCTION. Precisefunction:schema.name(argtypes)is deferred to v0.2. - Label.
label:<code>may exist but be facet-ambiguous. Preferlabel:<facet>.<code>. A bare code matching multiple facets →EXISTS+AMBIGUOUS_LABEL(treated as exists-with-warning, notMISSING). - Collection. Probe both physical (
information_schema.tables) and logical (directus_collections); record which matched inmetadata_jsonb; if only one side matches,EXISTS+LOGICAL_PHYSICAL_MISMATCH. - Procedure. Probe
pidx_procedurefirst, thenworkflows.process_code. Never merge the two code spaces (PROC_*self vs legacyWF-*).
7. Anti-false-green rules (the whole point)
- Only PG/SQL existence sets
READY.manifest_jsonb,declared_maturity,note,source_ref, seed status, and any future RAG/vector output may route/suggest but can never setREADY. - Warnings cannot be silently swallowed. All-required-EXISTS + any warning →
READY_WITH_WARNINGS(orREADY+ non-emptywarning_flags), never bareREADY. - Zero ingredients ≠ ready. A procedure with no ingredient rows computes
UNMAPPED, neverREADY. - UNKNOWN_SOURCE never fabricates existence.
io/checker/template/reportresolveUNKNOWN_SOURCE; if required, the procedure isNOT_READY(we cannot prove the ingredient). - Declared-ready that isn't computed-ready must shout.
READINESS_DRIFTfires whenever a ready-tierdeclared_maturitycontradicts aNOT_READY/UNMAPPEDcomputation. - No silent fuzzy join. An un-normalizable ref is
INVALID_REF, never guessed into a match.
8. v0.1 scope of this logic
- SAFE to compute now:
dot, collection, view, field, trigger, function, approval, event, procedure. - PARTIAL:
label(facet caveat). - Always
UNKNOWN_SOURCE:io, checker, template, report. - First v0.2 follow-ups:
functionsignature precision, a realio/checkerSSOT, a confirmedtemplatesource, and (only if scale demands)pg_trgm/vector as a two-step suggest→confirm radar that still never answers exists/missing/ready.