KB-3297

PIDX Readiness Logic v0.1 — ref normalization, status precedence, warning flags, anti-false-green

13 min read Revision 1
workflow-manageprocedure-indexpidxreadiness-logicref-grammarstatus-precedencewarning-flagsanti-false-greenv0.12026-06-23

PIDX Readiness Logic v0.1 — normalization, precedence, warnings, anti-false-green

Path: knowledge/dev/laws-new/workflow-manage/design/pidx-readiness-logic-v0.1.md Status: DESIGN · v0.1 · NON-AUTHORIZING · 0 PG objects. Specifies the computed semantics of v_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 set READY. A declaration, manifest, note, RAG result, or seed status can route and suggest but can NEVER set READY.


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 (+ STALE downgrade when the inventory branch reports refresh_required).
  • Grammar-locked — both pidx_procedure_ingredient.ingredient_ref and v_pidx_inventory_current.object_ref import 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.tablesdirectus_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_procpg_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_codeworkflows.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

MISSING vs UNKNOWN_SOURCE is 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 to MISSING.


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_tools with no column) is caught by rule 1 (structurally unparseable for that kind) → INVALID_REF, not a silent MISSING.


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> resolves EXISTS if apr_action_types contains the action. If its handler_ref is null/unimplemented, it still EXISTS (the gate is declared) but raises APPROVAL_HANDLER_UNIMPLEMENTED → the procedure becomes READY_WITH_WARNINGS, never silent READY. (Live: only patch_ops_code, create_item, update_item, add_field are implemented; the other 11 are unimplemented.)
  • Function. function:schema.name is name-only existence in v0.1. If the name is overloaded (count > 1), EXISTS + OVERLOADED_FUNCTION. Precise function:schema.name(argtypes) is deferred to v0.2.
  • Label. label:<code> may exist but be facet-ambiguous. Prefer label:<facet>.<code>. A bare code matching multiple facets → EXISTS + AMBIGUOUS_LABEL (treated as exists-with-warning, not MISSING).
  • Collection. Probe both physical (information_schema.tables) and logical (directus_collections); record which matched in metadata_jsonb; if only one side matches, EXISTS + LOGICAL_PHYSICAL_MISMATCH.
  • Procedure. Probe pidx_procedure first, then workflows.process_code. Never merge the two code spaces (PROC_* self vs legacy WF-*).

7. Anti-false-green rules (the whole point)

  1. 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 set READY.
  2. Warnings cannot be silently swallowed. All-required-EXISTS + any warning → READY_WITH_WARNINGS (or READY + non-empty warning_flags), never bare READY.
  3. Zero ingredients ≠ ready. A procedure with no ingredient rows computes UNMAPPED, never READY.
  4. UNKNOWN_SOURCE never fabricates existence. io/checker/template/report resolve UNKNOWN_SOURCE; if required, the procedure is NOT_READY (we cannot prove the ingredient).
  5. Declared-ready that isn't computed-ready must shout. READINESS_DRIFT fires whenever a ready-tier declared_maturity contradicts a NOT_READY/UNMAPPED computation.
  6. 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: function signature precision, a real io/checker SSOT, a confirmed template source, and (only if scale demands) pg_trgm/vector as a two-step suggest→confirm radar that still never answers exists/missing/ready.