Surfaced by #16929's census repair (PR #17191). Adding the orchestrator's task definitions as a third entrypoint channel brought aggregate-temporal-summary.mjs into the lint population for the first time, and it immediately produced the population's only authority conflict.
The conflict is real. The defect it exposes is in the lint's capability taxonomy, not in the code it convicts.
Reshaped 2026-08-24 after @neo-gpt-emmy's intake revalidation (#17217 comment 5395370512) retired this ticket's original falsifier. Her finding held under re-verification and the measurement below extends it: the ticket's root claim is unchanged, its host-side control is replaced, and one constraint is now measured that was previously assumed.
The Problem
ai/scripts/lint/scriptPlaneClosure.mjs maps host capabilities from bare specifiers:
temporal-summary is declared container-plane, so the lint reports authority-conflict-in-plane — "the script breaks on the plane it is declared for."
It does not break there, and ADR-0014 says so explicitly. From taskAuthority.mjs's own header:
kbSync and temporal-summary are container-plane, not host-edge, even though both scan the Neo repo's own corpus … The container IS the checkout: it is built from the repo and carries learn/, src/, resources/content/ and .git at the built revision, which is every source both lanes read.
The ADR goes further and names the failure mode of the opposite call: classing them host-edge "leaves the Knowledge Base with no producer at all … A lane whose decliner names itself as owner is the shape to watch for."
So the container has a shell and a git checkout. child_process discriminates "spawns a subprocess", which both planes can do. It does not discriminate plane.
Why this is the same defect one level up
#16929 exists because a directory name and a file header are both names, and a name can be wrong while the code moves on. The closure replaced them with a measurement — and then measured the wrong property. execSync('git log') and execFileAsync('docker', …) are the same syntax and different planes, and only the second one needs the host.
Authority delta: the original falsifier expired
This ticket was filed against a githubWorkflowSync / temporal-summary pair — one declared host-edge with a git-only requirement, one declared container-plane with the same. The naive predicate would have inverted the first while fixing the second, and that trade was the reason the fix was left undecided.
That pair no longer exists.#17627 / PR #17672 retired githubWorkflowSync as an orchestrator task. Verified at origin/dev4e6d8da73e: the string survives in exactly 3 places, all of them JSDoc prose (lint-script-plane.mjs:280-281, scriptPlaneClosure.mjs:697) describing a synthetic spec fixture. TASK_AUTHORITY_BY_NAME and buildTaskDefinitions() contain no such task.
A candidate replacement was considered and rejected: primary-dev-sync is declared host-edge and plausibly git-only, but it never enters the lint's population — buildAuthorityByScript() joins on the executed module from a task definition's args, and primary-dev-sync produces no .mjs script mapping. It cannot serve as a control.
The host arm cannot currently convict — measured, not inferred
Measured at origin/dev4e6d8da73e by driving walkCapabilityClosure() + resolveEntrypointPlane() over the full census (75 roots, 0 walk errors):
roots in the population
75
roots with zero unresolved edges
32
roots with ≥ 1 unresolved edge
43
host-edge roots in the authority-mapped population
2
roots where authorityConflictHost can fire today
0
The two mapped host-edge roots:
root
task
closure.required
findings
verdict
ai/daemons/wake/daemon.mjs
bridgeDaemon
1 (host-shell, spawnAsync, deferred)
unresolved-edge
no conflict — but its only host evidence is generic child_process, and the host effect (osascript / tmux / the configured Codex binary) is supplied by callers, which the walk does not propagate
ai/mcp/server/neural-link/run-bridge.mjs
neuralLinkBridge
0
unresolved-edge
declares host-edge, proves no host capability, and is not convicted
When closureIsHost is false, closureIsSound reduces to findings.length === 0. So the host arm requires a host-edge root with zero unresolved edges. Both mapped host-edge roots carry ai/ConfigProvider.mjs::dynamic-import::load — an accepted, ledgered edge. The 32 zero-edge roots all carry no authority declaration, so the arm's first term excludes them.
The suppression is correct. The guard exists because a "no host requirement" verdict from an incomplete closure is unsound — the capability could live behind the edge that could not be followed. The problem is not the guard; it is that this ticket assumed a host-side falsifier existed, and at this ref none does. Emmy's intake said the naive repair "false-greens the host positive control"; the sharper statement is that the host arm makes a claim it cannot currently back for any root — before any predicate change.
This is a reachability claim about 4e6d8da73e, not a permanent verdict: a future host-edge task mapping to a zero-edge root would make the arm fire.
The Fix
Still undecided on purpose — this is an ADR-0014-owned question. The naive predicate (grant host-shell only when the spawned binary is host-controlling: docker, podman, systemctl) is still the leading candidate for the container half, and it still fixes temporal-summary. What changed is that its host-side cost can no longer be paid by an existing pair, so the host half needs a decision. Three routes, cheapest first:
Resolve the suppressor for one host-edge root.ai/ConfigProvider.mjs::dynamic-import::load is the only thing standing between neuralLinkBridge and a conviction the lint should already be making. Resolving it for one host-edge root makes the host arm assertable at all, and is a prerequisite for either route below rather than an alternative to them.
Model host effect / argument flow, so bridgeDaemon's real requirement (osascript, tmux, a configured binary) survives a narrowed predicate instead of vanishing with the generic child_process evidence.
Make the asymmetry explicit. Amend ADR-0014 and the lint's own documented claim to state that host-edge authority is not independently reproducible from this closure, and that the gate convicts only in the container direction.
A literal binary allow/deny list alone cannot encode read the built checkout versus actuate/mutate the maintainer host; that distinction is the ADR's to make.
Acceptance Criteria
The predicate that grants host-shell is stated in terms of what the container plane actually lacks, and the statement is sourced from ADR-0014 rather than inferred from the import.
temporal-summary resolves with no authority conflict, and the reason is a corrected predicate — not an exemption, a widened taxonomy, or a ledger entry.
The host arm has a reachable control. At least one root in the mapped population can produce authorityConflictHost, red-proved by mutation, or route 3 is taken and the non-reproducibility is recorded in ADR-0014 and in the lint's own JSDoc claim. A count of zero convictable roots must not survive this ticket silently.
neuralLinkBridge's host-edge declaration is dispositioned explicitly: either its closure reproduces a host requirement under the new predicate, or the declaration changes, or it is recorded as authority the closure cannot verify. It currently proves nothing and is saved from a verdict only by a ledgered edge.
Red-proved: a fixture spawning docker resolves host-edge and a fixture spawning git does not, at the same call syntax.
KNOWN_AUTHORITY_CONFLICTS in lint-script-plane.mjs is emptied by this work and its entry removed, not re-pointed at a successor ticket.
Out of Scope
The proof boundary.#16929 owns the invocation walk; this ticket owns only which capabilities imply which plane.
Re-classing any task. If the outcome is that a declaration must change, that is a follow-up with the ADR owner in the loop, not a drive-by edit to taskAuthority.mjs.
The unresolved-edge ledger. Different population, different mechanism — except for the single named suppressor in route 1, which is in scope only as a prerequisite.
The stale-authority predicate. PR #17706 makes the held-conflict ledger self-retiring once this conflict is genuinely resolved; it does not supply the missing predicate and is not blocked on this ticket.
Avoided Traps
Reading the conflict as a bug in the convicted code. The lane is fine; the ruler is wrong. The first repair considered was re-classing temporal-summary to host-edge, which ADR-0014 pre-emptively argues against by name.
Fixing it inside #16929. The predicate change is not local to the lint — it is a claim about the topology, and PR #17191 would have shipped it as a side effect of a census repair.
Treating "no conflicts" as the goal. The gate is worth having only if its verdicts are reproducible from the ADR; a taxonomy tuned until the board is green is the hand-authored metadata this lane exists to remove.
Assuming a falsifier still exists because the ticket named one. This ticket carried a host-side control for nine days after #17672 removed it. The reshape's own measurement is the guard: count the convictable roots, do not name one.
Treating the closureIsSound suppression as the defect. It is a correct soundness guard. Removing it to make the host arm fire would trade a silent non-verdict for an unsound one.
Evidence class
L3 — executable measurement at a named ref, not a static read. walkCapabilityClosure() + resolveEntrypointPlane() driven in-process over the full 75-root census at origin/dev4e6d8da73e; the 2-root host-edge population, both closure.required counts, both suppressor edge identities, their ledger membership, and the 0-convictable-roots count are all tool output. The githubWorkflowSync retirement was verified by source search against the live authority map, not from the intake description.
Related
#16929 (the lane that surfaced it) · PR #17191 · ADR-0014 (learn/agentos/decisions/0014-cloud-deployment-topology-and-scheduler-task-taxonomy.md) · #17171 (the same lint's required-context gap) · #17627 / PR #17672 (the authority delta) · PR #17706 (the stale-authority predicate, independent)
Live latest-open sweep of all 341 open issues at 2026-08-15T21:38Z on child_process|host-shell|taxonom|capability class|host-edge|subprocess: no duplicate. No competing A2A [lane-claim].
Retrieval Hint: query_raw_memories("child_process is not a plane predicate container has git ADR-0014 temporal-summary conflict") · falsification anchor: HOST_CAPABILITY_SOURCES in ai/scripts/lint/scriptPlaneClosure.mjs, against taskAuthority.mjs's kbSync/temporal-summary paragraph · host-arm reachability anchor: the closureIsSound term in resolveEntrypointPlane.
Context
Surfaced by #16929's census repair (PR #17191). Adding the orchestrator's task definitions as a third entrypoint channel brought
aggregate-temporal-summary.mjsinto the lint population for the first time, and it immediately produced the population's only authority conflict.The conflict is real. The defect it exposes is in the lint's capability taxonomy, not in the code it convicts.
Reshaped 2026-08-24 after @neo-gpt-emmy's intake revalidation (#17217 comment 5395370512) retired this ticket's original falsifier. Her finding held under re-verification and the measurement below extends it: the ticket's root claim is unchanged, its host-side control is replaced, and one constraint is now measured that was previously assumed.
The Problem
ai/scripts/lint/scriptPlaneClosure.mjsmaps host capabilities from bare specifiers:export const HOST_CAPABILITY_SOURCES = Object.freeze({ [HOST_CAPABILITY.shell] : Object.freeze(['child_process']), [HOST_CAPABILITY.socket]: Object.freeze(['dockerode', 'docker-modem']) });So any
child_processuse that a proven invocation chain reaches makes an entrypoint host-required. The closure proves that chain six members deep:aggregate-temporal-summary.mjs::main TemporalSummaryAggregationService::runCycle ::collectPendingWindows ::fetchWindowSources ::fetchDevCommits ::execCommand -> execSync('git log --first-parent origin/dev …')temporal-summaryis declaredcontainer-plane, so the lint reportsauthority-conflict-in-plane— "the script breaks on the plane it is declared for."It does not break there, and ADR-0014 says so explicitly. From
taskAuthority.mjs's own header:The ADR goes further and names the failure mode of the opposite call: classing them host-edge "leaves the Knowledge Base with no producer at all … A lane whose decliner names itself as owner is the shape to watch for."
So the container has a shell and a git checkout.
child_processdiscriminates "spawns a subprocess", which both planes can do. It does not discriminate plane.Why this is the same defect one level up
#16929 exists because a directory name and a file header are both names, and a name can be wrong while the code moves on. The closure replaced them with a measurement — and then measured the wrong property.
execSync('git log')andexecFileAsync('docker', …)are the same syntax and different planes, and only the second one needs the host.Authority delta: the original falsifier expired
This ticket was filed against a
githubWorkflowSync/temporal-summarypair — one declaredhost-edgewith a git-only requirement, one declaredcontainer-planewith the same. The naive predicate would have inverted the first while fixing the second, and that trade was the reason the fix was left undecided.That pair no longer exists. #17627 / PR #17672 retired
githubWorkflowSyncas an orchestrator task. Verified atorigin/dev4e6d8da73e: the string survives in exactly 3 places, all of them JSDoc prose (lint-script-plane.mjs:280-281,scriptPlaneClosure.mjs:697) describing a synthetic spec fixture.TASK_AUTHORITY_BY_NAMEandbuildTaskDefinitions()contain no such task.A candidate replacement was considered and rejected:
primary-dev-syncis declaredhost-edgeand plausibly git-only, but it never enters the lint's population —buildAuthorityByScript()joins on the executed module from a task definition'sargs, andprimary-dev-syncproduces no.mjsscript mapping. It cannot serve as a control.The host arm cannot currently convict — measured, not inferred
Measured at
origin/dev4e6d8da73eby drivingwalkCapabilityClosure()+resolveEntrypointPlane()over the full census (75 roots, 0 walk errors):host-edgeroots in the authority-mapped populationauthorityConflictHostcan fire todayThe two mapped
host-edgeroots:closure.requiredai/daemons/wake/daemon.mjsbridgeDaemonhost-shell,spawnAsync, deferred)unresolved-edgechild_process, and the host effect (osascript/tmux/ the configured Codex binary) is supplied by callers, which the walk does not propagateai/mcp/server/neural-link/run-bridge.mjsneuralLinkBridgeunresolved-edgehost-edge, proves no host capability, and is not convictedThe mechanism is in
resolveEntrypointPlane:const closureIsSound = closureIsHost || findings.length === 0; … } else if (authorityIsHost && !closureIsHost && closureIsSound) { // authorityConflictHostWhen
closureIsHostis false,closureIsSoundreduces tofindings.length === 0. So the host arm requires ahost-edgeroot with zero unresolved edges. Both mapped host-edge roots carryai/ConfigProvider.mjs::dynamic-import::load— an accepted, ledgered edge. The 32 zero-edge roots all carry no authority declaration, so the arm's first term excludes them.The suppression is correct. The guard exists because a "no host requirement" verdict from an incomplete closure is unsound — the capability could live behind the edge that could not be followed. The problem is not the guard; it is that this ticket assumed a host-side falsifier existed, and at this ref none does. Emmy's intake said the naive repair "false-greens the host positive control"; the sharper statement is that the host arm makes a claim it cannot currently back for any root — before any predicate change.
This is a reachability claim about
4e6d8da73e, not a permanent verdict: a futurehost-edgetask mapping to a zero-edge root would make the arm fire.The Fix
Still undecided on purpose — this is an ADR-0014-owned question. The naive predicate (grant
host-shellonly when the spawned binary is host-controlling:docker,podman,systemctl) is still the leading candidate for the container half, and it still fixestemporal-summary. What changed is that its host-side cost can no longer be paid by an existing pair, so the host half needs a decision. Three routes, cheapest first:ai/ConfigProvider.mjs::dynamic-import::loadis the only thing standing betweenneuralLinkBridgeand a conviction the lint should already be making. Resolving it for one host-edge root makes the host arm assertable at all, and is a prerequisite for either route below rather than an alternative to them.bridgeDaemon's real requirement (osascript,tmux, a configured binary) survives a narrowed predicate instead of vanishing with the genericchild_processevidence.host-edgeauthority is not independently reproducible from this closure, and that the gate convicts only in the container direction.A literal binary allow/deny list alone cannot encode read the built checkout versus actuate/mutate the maintainer host; that distinction is the ADR's to make.
Acceptance Criteria
host-shellis stated in terms of what the container plane actually lacks, and the statement is sourced from ADR-0014 rather than inferred from the import.temporal-summaryresolves with no authority conflict, and the reason is a corrected predicate — not an exemption, a widened taxonomy, or a ledger entry.authorityConflictHost, red-proved by mutation, or route 3 is taken and the non-reproducibility is recorded in ADR-0014 and in the lint's own JSDoc claim. A count of zero convictable roots must not survive this ticket silently.neuralLinkBridge'shost-edgedeclaration is dispositioned explicitly: either its closure reproduces a host requirement under the new predicate, or the declaration changes, or it is recorded as authority the closure cannot verify. It currently proves nothing and is saved from a verdict only by a ledgered edge.dockerresolves host-edge and a fixture spawninggitdoes not, at the same call syntax.KNOWN_AUTHORITY_CONFLICTSinlint-script-plane.mjsis emptied by this work and its entry removed, not re-pointed at a successor ticket.Out of Scope
taskAuthority.mjs.Avoided Traps
temporal-summarytohost-edge, which ADR-0014 pre-emptively argues against by name.closureIsSoundsuppression as the defect. It is a correct soundness guard. Removing it to make the host arm fire would trade a silent non-verdict for an unsound one.Evidence class
L3 — executable measurement at a named ref, not a static read.
walkCapabilityClosure()+resolveEntrypointPlane()driven in-process over the full 75-root census atorigin/dev4e6d8da73e; the 2-root host-edge population, bothclosure.requiredcounts, both suppressor edge identities, their ledger membership, and the 0-convictable-roots count are all tool output. ThegithubWorkflowSyncretirement was verified by source search against the live authority map, not from the intake description.Related
#16929 (the lane that surfaced it) · PR #17191 · ADR-0014 (
learn/agentos/decisions/0014-cloud-deployment-topology-and-scheduler-task-taxonomy.md) · #17171 (the same lint's required-context gap) · #17627 / PR #17672 (the authority delta) · PR #17706 (the stale-authority predicate, independent)Live latest-open sweep of all 341 open issues at 2026-08-15T21:38Z on
child_process|host-shell|taxonom|capability class|host-edge|subprocess: no duplicate. No competing A2A[lane-claim].Origin Session ID: 5cd926fa-77e1-4309-8bbf-ca563ab07403 Reshape Session ID: cad88c79-073f-4816-aaa7-e779224f2af3
Retrieval Hint:
query_raw_memories("child_process is not a plane predicate container has git ADR-0014 temporal-summary conflict")· falsification anchor:HOST_CAPABILITY_SOURCESinai/scripts/lint/scriptPlaneClosure.mjs, againsttaskAuthority.mjs'skbSync/temporal-summaryparagraph · host-arm reachability anchor: theclosureIsSoundterm inresolveEntrypointPlane.