feat(runner): report a run.flow that resolves in no loaded skill tier - #190
Merged
Conversation
…#189) The trigger's run.flow reached the container only as prompt prose, and pi never matches prose against loaded skill names, so a flow that materialised in no tier (repo, injected, overlay or staged package) ran to a clean exit 0 without the procedure it was written for and reported success for work it could not have done. The worker now forwards the flow structurally as PI_FLOW (omitted when the job carries none, never an empty string; env and not event.json, because an execution knob is not a fact about the delivery), and the runner compares it against the skill set the loader actually materialised, immediately after resource load and before any session or spend, emitting one flow_not_loaded line on a miss. Report, not refusal, deliberately: run.flow is by long doctrine a prompt hint, and a refusal shipped in an image upgrade would fail yesterday's jobs for a value the reviewed file has carried all along. The check sits at the pre-spend moment, so flipping report to refusal later is a one-line change at the same site. getSkills() moves out of the packages-only guard so the check runs for every job. isFlowLoaded is exact name equality against the LOADED names (pi names a skill frontmatter name || parent dir at the pin, now pinned by a loader test), and a disableModelInvocation skill counts as loaded: it is absent from the catalogue but invocable, so it is not the silent no-op this line exists to catch. Specs: INT-CONTAINER-JOB-INPUTS AMENDED (PI_FLOW), REQ-PER-TRIGGER-SKILLS AMENDED (the runner acceptance clause), NEW DES-FLOW-RESOLUTION-TWO-ADVISORY-LAYERS (report-not-refuse and the rejected alternatives: boot-time failure, event.json carriage, a new top-level outcome). UNCHANGED, checked: INT-RUNNER-EXIT-CODE-PROTOCOL, INT-TRIGGERS-FILE-CONTRACT, INT-WEBHOOK-PAYLOAD-SUBSET, REQ-GLOBAL-PI-OVERLAY, REQ-DEPLOYMENT-BOOTSTRAP, DES-AI-TRIGGER-FLOW-GATE, DES-TRIGGERS-UNIFIED-FILE. Signed-off-by: Rob Boerman <robboerman@live.nl>
edgehero
added a commit
that referenced
this pull request
Aug 13, 2026
The doctor half of issue #189 Gap 1 (the runner half merged as #190): one line per distinct (flow, folder, skillsDir, packages) question, naming the skill tier that resolves the trigger's run.flow, probed in the loader's own precedence order. The repo tier reuses the flow gate's ls-tree mechanics (100644-blob rule, hardened git flags) but deliberately NOT readFlowGate itself: the gate's fail-closed catch turns a broken folder into deny, and deny-implies-exists would print a confident wrong answer on an advisory line; its no-ref rule guards against an agent self-authorizing, which a host-side preflight does not face, so doctor resolves HEAD itself and degrades to unknown. The staged tier gets the genuinely new reader, readStagedSkills in worker/src/packages.mjs (never-throws, reachable by admin for issue #188's topology): it mirrors pi's manifest-vs-convention rule at the 0.80.7 pin, including that a pi manifest WITHOUT a skills key contributes nothing, and declares a pattern-carrying manifest not-enumerable rather than guessing, because patterns can also disable files and a wrong checkmark is the one direction an advisory line may not err in. When nothing resolves the line is a warn, never a fail and never a fixAction (triggers content is the never tier), naming the tiers checked and the ones not checkable on this host: a forge trigger's repo is not here, and mid-setup is legal. Staged-package-only resolution is a plain checkmark naming the package, per the issue's acceptance. A flow failing the skill charset is its own warn. Zero triggers add zero lines; the two package-feature no-op pins keep their broad needle by stripping the flow lines, which name staged packages as a tier. Specs: REQ-PER-TRIGGER-SKILLS AMENDED (the doctor acceptance clause), REQ-GLOBAL-PI-OVERLAY AMENDED (overlay + staged tiers join doctor's obligations), DES-FLOW-RESOLUTION-TWO-ADVISORY-LAYERS AMENDED (the doctor layer specified in full, readFlowGate rejection recorded). UNCHANGED, checked: REQ-DEPLOYMENT-BOOTSTRAP (warn-tier, no fixAction), INT-TRIGGERS-FILE-CONTRACT, INT-CONTAINER-JOB-INPUTS, INT-PI-PACKAGES-FILE-CONTRACT, DES-AI-TRIGGER-FLOW-GATE, DES-CLI-SURFACE. Signed-off-by: Rob Boerman <robboerman@live.nl>
This was referenced Aug 13, 2026
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.
Gap 1, runner half of #189. The doctor half (per-trigger tier resolution lines) follows as its own PR.
What
A trigger's
run.flowreached the container only as prompt prose, and pi never matches prose against loaded skill names, so a flow that materialised in no tier (repo, injected, overlay or staged package) ran to a clean exit 0 without the procedure it was written for. This PR makes that miss leave a named, greppable trace:PI_FLOW(omitted when the job carries none, never an empty string; env and notevent.json, because an execution knob is not a fact about the delivery, therun.replicasline).flow_not_loadedline on a miss.getSkills()moves out of the packages-only guard so the check runs for every job.run.flowis by long doctrine a prompt hint, and a refusal shipped in an image upgrade would fail yesterday's jobs for a value the reviewed file has carried all along. The check sits at the pre-spend moment, so flipping report to refusal later is a one-line change at the same site. Recorded with the rejected alternatives on the newDES-FLOW-RESOLUTION-TWO-ADVISORY-LAYERS.Acceptance mapping (#189)
flow_not_loadedline, flow name and a loaded-skill count, never task content.openSessionManager(pre-spend), andgetSkills()outside the packages guard.name|| parent dir), the premise the comparison rests on;disableModelInvocationskills count as loaded (invocable, so not the silent no-op this line exists to catch).Specs
INT-CONTAINER-JOB-INPUTSAMENDED (PI_FLOW),REQ-PER-TRIGGER-SKILLSAMENDED (runner acceptance clause), NEWDES-FLOW-RESOLUTION-TWO-ADVISORY-LAYERS. UNCHANGED, checked:INT-RUNNER-EXIT-CODE-PROTOCOL,INT-TRIGGERS-FILE-CONTRACT,INT-WEBHOOK-PAYLOAD-SUBSET,REQ-GLOBAL-PI-OVERLAY,REQ-DEPLOYMENT-BOOTSTRAP,DES-AI-TRIGGER-FLOW-GATE,DES-TRIGGERS-UNIFIED-FILE.Suite: 2202 tests, 0 failed, 0 skipped, in the CI posture (loader/worker/receiver gates on, live Valkey).