The one registry of where a model takes part in the Factory, and the pure selector that binds a declared task to its pinned model and effort. Every module in src/, and every planned one (including the nine of the proposed lifecycle design), declares here whether a model is part of its interface (none, optional or required) and why. It dispatches nothing.
Status: Accepted and merged to main at 298726b (independent implementation review by Astra high: round 2 PASS, <local evidence archive>). Its design went through five independent Astra rounds (<local evidence archive>); round 5 left two findings on the test's commission check, resolved by using the Producers module's own commission parser instead of a copy. An independent adversarial pass then found seven defects (its tests are test/intelligence-adversarial.test.ts; report and logs under <local evidence archive>), all fixed here. Independent implementation review by Astra: <local evidence archive>, where the highest-numbered report holds the current verdict. Lifecycle increment 0 (branch factory/lifecycle, from e62abe6) changes the module and passed its independent review (Astra round 2 PASS, <local evidence archive>); its receipt is intelligence-lifecycle-receipt.json. Lifecycle increment 1 (branch factory/lifecycle-schedule) moves one declaration with its file and also passed independent review (Astra round 4 PASS, <local evidence archive>). Lifecycle increment 2 (branch factory/lifecycle-sources) then moves the discovery-sources declaration into MODULE_INTELLIGENCE with its two files and passed independent review with that module (Astra round 5 PASS, <local evidence archive>); the increment-0 receipt's hash of src/intelligence.ts no longer matches the file. The merge with main (branch factory/lifecycle-merge) moves the planned rows out of the README table into this reference; its receipt is lifecycle-merge-receipt.json, and its independent review is <local evidence archive>, where the highest-numbered report holds the current verdict. The merge with service access (branch factory/service-access-merge) adds that module's none declaration; its receipt is service-access-merge-receipt.json, so the lifecycle merge receipt's hash of src/intelligence.ts no longer matches the file, and its independent review is pending. No model is qualified, so every task refuses today.
- Source:
src/intelligence.ts - Tests:
test/intelligence.test.ts(13),test/intelligence-adversarial.test.ts(6),test/intelligence-lifecycle.test.ts(7),test/intelligence-lifecycle-adversarial.test.ts(8) - Rendered views: the Intelligence table in the README, generated by
renderIntelligenceTable()for modules whose code exists (accepted, or in review with their increment), and the planned declarations below, the handover steps' generated byrenderPlannedIntelligenceTable()and the lifecycle's byrenderLifecycleIntelligenceTable(); each task's pin, escalation, protocol, bounds and measure are read in the source, not restated here
Intelligence: none — This registry and selector: which model runs must be reproducible from the record, so it is frozen data and a pure function.
What it hides
- Where a model may take part.
MODULE_INTELLIGENCE(modules whose code exists: accepted ones, includinglifecycle-scheduleanddiscovery-sources, each accepted with its increment, and one in review,service-access, provisionally accepted with its increment 1 while its Astra review is pending; with their exact src-relative file paths;product-gateholdsproduct-gate.tsanddelivery-records.ts, as its reference and build-review row do),PLANNED_INTELLIGENCE(handover steps 1–4;planningalso names the proposed lifecycle'splanning/opportunity-gate.ts) andLIFECYCLE_INTELLIGENCE(the lifecycle's other planned modules whose code has not landed, §2; increment 1 movedlifecycle-scheduleintoMODULE_INTELLIGENCEwithlifecycle-schedule.ts, and increment 2discovery-sources, namingdiscovery-sources.tsanddiscovery-text.ts). No accepted file changes to declare. - Which model and effort each task runs. Each task pins one provider, exact model, effort and transport, dated, plus at most one escalation. Callers never choose a model. Every task of an accepted or step-planned module is
frontier:producer/proposepinsCLAUDE_MODEL, the one model the Claude transport binds, and the five other earlier tasks pin the most capable modelMODEL_TIERSlists. The lifecycle's five tasks pin the spec's defaults as hypotheses:claude-sonnet-5low (efficient) for triage, draft packs and, under the research profile, search;claude-opus-5-5high for synthesis and retirement cases. Its three Astra tasks areBLOCKED_TASKS:gpt-6-astraxhigh throughcodex-cliis only proposed, since no pin can name an effort that route does not establish. - What is known about each model.
MODEL_TIERS: dated facts per model and transport (accepted efforts, whether the transport can count provider requests, qualification, a reference note) and the candidate (model, effort) pairs of each tier. Claude models are listed onclaude-cliand, for Sonnet 5 only, on the research routeclaude-cli-research; OpenAI's oncodex-cli, with no established effort.
Public interface
| Name | What |
|---|---|
INTELLIGENCE_PROTOCOL | "sf-intelligence/1", inside every policy digest |
EFFORTS, PHASES, MAX_WHY_LENGTH (160), MAX_TEXT_LENGTH (400), MAX_REASON_LENGTH (500); types Transport, Allowance | frozen vocabularies, prose bounds and the refusal-reason bound (PRODUCER_LIMITS.textLength). Transports: claude-cli and openai-responses (step 1's, with accepted Producers transports), codex-cli and claude-cli-research (declared, none built); Claude models may be listed on claude-cli or claude-cli-research, OpenAI models only on codex-cli, so a model listed on openai-responses is refused at load and the accepted OpenAI Responses adapter is kept but unpinned. Allowances: slice, product, improvement-window or discovery-window, a Factory admission limit for discovery occurrences, never a reservation of provider capacity (lifecycle §5) |
TRANSPORT_CEILINGS | PRODUCER_LIMITS restated: enforced inputBytes 1 310 720, outputBytes 1 048 576, deadlineMs 600 000; requested maxOutputTokens 128 000, maxCostCents 2 000 |
MODULE_INTELLIGENCE, PLANNED_INTELLIGENCE, LIFECYCLE_INTELLIGENCE, BLOCKED_TASKS, MODEL_TIERS, REGISTRY | the deep-frozen registry; REGISTRY holds all three declaration lists and the blocked tasks. A BlockedTask carries a task's descriptive fields and a proposed pin, with no bounds, protocol, basis or policy, and becomes a task by a reviewed edit once its route lists the proposed effort |
taskPolicyDigest(taskId, registry?) | "sha256:" over the canonical form of the named fields the selector enforces (id, role, phase, caller, protocol, tier with an efficient task's basis, pin, escalation, bounds with the estimate's figure, allowance, and its pinned models' provider, model, transport, efforts, request count and qualification state with its commission reference). Prose (notes, qualification reasons, the estimate's basis) is excluded. TypeError for a malformed or unknown task id, a blocked task, or an invalid registry |
chooseModel(request, registry?) | {choice} or {refused, reason}; TypeError for a malformed request or an invalid registry. A verifier's choice records the producer identity it was checked against |
renderIntelligenceTable(), renderPlannedIntelligenceTable(), renderLifecycleIntelligenceTable(), renderIntelligenceLine(module) | the README table, the two planned tables below and a module page's one line |
A task's bounds keep three things apart: enforced (host-enforced before contact), requested (provider-side ceilings a transport may only estimate) and estimate (a planning figure for the gate's reservation, never a limit; present exactly when the protocol is specified).
chooseModel checks in this order: request shape (TypeError), the registry when one is given (TypeError), unknown-task (blocked when the id is a blocked task's), producer-unknown (a verifier without the Candidate's producer, or naming a model MODEL_TIERS does not list for that provider; a non-verifier carrying one is a TypeError), not-specified, not-measured, escalation-undeclared, escalation-mismatch, pin-mismatch, not-qualified, policy-mismatch, commission-mismatch, implicit-retries, not-independent.
Invariants and guarantees
- Every module file under
src/, at any depth (.ts,.mts,.cts,.tsx,.js,.mjs,.cjs,.jsx,.json,.node,.wasm), belongs to exactly one accepted declaration, so a new source cannot land undeclared; a planned source that exists fails the test until its declaration moves intoMODULE_INTELLIGENCE. The guarded fixture sources staynone. - The registry is checked when the module loads, and a registry given in its place is checked on every call: unique module slugs and task IDs (
<module>/<name>), calendar dates, one-line bounded prose, src-relative sources declared once and sorted, tasks exactly whenoptionalorrequired, every pin and escalation a listed model at an accepted effort and a candidate of the task's tier, each model on one of its provider's transports (nothing onopenai-responses), only a drafter onclaude-cli-research(the research profile has a tool), one of the four allowances, each blocked task's id unique among task IDs under a module that declares intelligence and its proposed model listed and unqualified on a route that lists no such effort (otherwise it must be a task), bounds withinTRANSPORT_CEILINGS, and a complete commission reference for any qualified model. A bad edit throws at import. - A task's caller is a declared module that itself declares intelligence, never one declared
none. The rule never asks whether the caller has landed, so a planned declaration that moves intoMODULE_INTELLIGENCEstays valid. The exchange runs through a Producers-module transport the caller is handed, never imports (step-1 spec §7 D1). chooseModelis pure and deterministic. It reads each request field once, from own data properties (an accessor is aTypeError), into a validated copy it alone uses; fields it does not name are never read or returned; its results are fresh and frozen, and it never freezes an object its caller passed in. The module imports onlynode:cryptoand./canonical-json.ts, reads no clock, randomness, environment or file, names no identifiermoduleorrequire(test/delivery-boundaries.test.tsrefuses both insrc/, so themodulekey is written as a string), and no other module file imports it.- It never substitutes a model, lowers or raises an effort, or retries: any difference from the pin is
pin-mismatch. Anefficienttask whose basis is notmeasuredrefusesnot-measured, whatever its model's qualification. - A frozen request carries the task's policy digest and the commission digest it pinned; both must equal the registry's now, so a reviewed edit never silently changes what already-frozen work runs, and a wording edit never invalidates it.
- A model is
qualifiedonly with a committeddocs/<provider>-producer-commission.json. The test checks it withparseProducerCommissionandcommissionDigestfromsrc/producer.ts; binds it as the transport would (provider, the transport's one model, its policy digest, and for Claude exactly the managed-policy sources the transport observes on this host, in order); requires a date no later thanMODEL_TIERS.asOf; and requires the commission and its receipt to be regular files inside the repository, reached through no symbolic link and committed at HEAD with the recorded bytes and digests. - A transport whose provider request count is unknown (the Claude CLI today) cannot show "no implicit retry", so its models refuse
implicit-retriesuntil a commission establishes one request per invocation. - A verifier needs the Candidate's producer identity from its produce admission (
produce:prod-<48 hex>), naming a modelMODEL_TIERSlists for that provider, and never runs on the producing model; an authored identity names no provider or model. - Escalation is the one declared step: the pinned model at a strictly higher effort, requested exactly, from the base pin only, naming the failed dispatch (32 hex); it cannot compound.
- It agrees with the accepted transports (tested):
TRANSPORT_CEILINGSequalsPRODUCER_LIMITSandMAX_REASON_LENGTHitstextLength; eachclaude-clifact's request-count capability is the Claude transport's, andCLAUDE_MODELlists exactly its efforts; the producer pinsCLAUDE_MODEL. No model is listed on the accepted OpenAI Responses transport.codex-cliandclaude-cli-researchhave no Producers transport, so their facts have anunknownrequest count and no qualification, and the test's commission check refuses any commission for them; thecodex-clifact lists no effort, the research fact the Claude CLI's. - The README section is at most 25 lines: a heading, one paragraph and
renderIntelligenceTable(), one row per declaration whose code exists (accepted, or in review);renderPlannedIntelligenceTable()andrenderLifecycleIntelligenceTable()each sit exactly once below, so every declaration renders in exactly one of the three tables. Each accepted module's page here carriesrenderIntelligenceLine()exactly once, and no other line of any page here mentions one.
Failure semantics
- A malformed request or registry throws
TypeError; every other outcome is a value. A refusal's reason is one line of at most 500 characters (longer registry prose is cut with an ellipsis), built only from grammar-checked identifiers and bounded registry text; the caller records it. Nothing runs for a refusal. - Today every task refuses: the ten extensions (five earlier, five from the lifecycle) with
not-specified, andproducer/proposewithnot-qualified(thenimplicit-retrieswhile the Claude CLI's request count is unknown); each blocked task refusesblocked, always. With every protocol specified, the threeefficienttasks would refusenot-measuredand every other tasknot-qualified; with those three measured too, they would refusenot-qualified, because no model is qualified (tested).
Trust scope
Established, by test: coverage of src/, the registry check, pin, policy and commission binding, verifier placement and independence against the identity supplied, the escalation shape, the efficient tier's measurement gate, the blocked tasks' checks and refusal, purity, single reads and freezing, bounded reasons, agreement with the producer transports, and README and page sync.
Not established:
- Nothing calls
chooseModelyet, and the transports do not consult the registry; today the Claude transport's ownCLAUDE_MODELcheck is what refuses another model. - That no module outside the Producers module imports a transport is step-1 spec §7 D1, asserted by
test/delivery-boundaries.test.ts, not by this module's tests. - Review checks stay reserved in the accepted code (
judgeRepositoryjudges them inconclusive and the Product gate refuses review dispatches), matchingrepository-assurance/review's extension status. - The producer identity is trusted as supplied; its authenticity comes from the produce admission the caller reads (step 1).
- Single-use escalation admission, reservation and allowances belong to the Product gate and are untested here.
- Model facts are a dated copy of the Claude API reference (cached 2026-06-24), not live; every pin is an unmeasured hypothesis.
- No model is qualified, so the committed-commission check has run only against a temporary repository in its test. What the lifecycle declarations leave unestablished is listed under Lifecycle increment 0.
Composition
- Depends on:
node:cryptoand./canonical-json.tsonly. It must not import a producer: outside the Producers module nosrc/file may reach one. - Consumers: none in
src/.test/intelligence.test.tsreadssrc/producer.ts,src/claude-producer.tsandsrc/openai-producer.tsto check agreement. - Planned (step 1): before admitting a produce Run, the composition loads the commission, computes its digest and calls
chooseModelwith the frozen Slice's policy digest; a refusal becomes outcomeunavailableand nothing is dispatched.planSlicerecordstaskPolicyDigestbeside the pins it freezes. Repository review derives the producer identity from the produce admission. The lifecycle's plan is under Lifecycle increment 0.
Changing it safely
- Run
node --test test/intelligence*.test.tsandnpm run typecheck;npm run checkruns everything. - A change to a pin, escalation, bounds or estimate figure, allowance, protocol, caller, tier, an
efficienttask's basis, role, phase or a pinned model's enforced facts (including qualification) changestaskPolicyDigest, so frozen requests refuse withpolicy-mismatchby design. Date it (since,asOf). - A new
src/file lands with its declaration. A planned module moves intoMODULE_INTELLIGENCEwith the files that actually land; no other rule depends on whether a module has landed. - Qualifying a model is a person's act: commit the commission file and its receipt, then record
{path, sha256 (bare hex of its bytes), digest (commissionDigest)}. Acodex-cliorclaude-cli-researchmodel can qualify only after its Producers transport, audit and commission exist and the test's binding table names that transport. - An
efficienttask, a cascade or an escalation to another model needs a measured comparison with the pinned model at lower effort on the same tasks, judged by cost per completed task, and a reviewed change to the escalation rule; until anefficienttask's basis ismeasured,chooseModelrefuses itnot-measured. - When a
why, decision, task or pinned effort changes, regenerate the README table, the lifecycle table below and the page lines, rebuild the docs site, and get a fresh independent review. The README section stays within 25 lines: its prose is one paragraph, and further planned declarations render outside it.
Lifecycle increment 0
Increment 0 of the proposed lifecycle (§9, row 0), on branch factory/lifecycle from e62abe6. Implemented by Opus 5.5; an independent adversarial pass then found eight defects (its tests are test/intelligence-lifecycle-adversarial.test.ts; report, logs and dispositions under <local evidence archive>), all fixed here. Independent Astra review: <local evidence archive>, where the highest-numbered report holds the current verdict; round 1 failed on one finding (the Astra tasks were left out rather than declared blocked), fixed here. Receipt: intelligence-lifecycle-receipt.json.
- Planned declarations. The nine modules of spec §2 with their decision and reason:
planninginPLANNED_INTELLIGENCE, now namingplanning/opportunity-gate.ts, and the other eight inLIFECYCLE_INTELLIGENCE.lifecycle-schedule,discovery-sources,owner-decisionsandoutcome-validationarenone;signal-intake,opportunity-synthesis,growthandcapability-retirementareoptional, with five tasks (triage, search, synthesis, the draft pack and the retirement case) and three blocked tasks (below). Each task is a protocol extension, a plan-phase drafter its own module requests, with no escalation and basishypothesis. - The three Astra tasks are declared blocked. Spec §4 also names
signal-intake/audit,planning/challenge-opportunityandgrowth/review-pack, atgpt-6-astraxhigh (decision 4's default).codex exechas no dedicated effort option: today's hand-run Astra reviews pass a generic configuration override (-c model_reasoning_effort=…), which the designed T2 argv (spec §8, with--ignore-user-config) does not include and no audit covers. So no effort is established oncodex-cli: its fact lists none, asclaude-haiku-4-5's does, and a pin must name an effort its fact lists (invariant 2). The three are thereforeBLOCKED_TASKS, with every descriptive field, their caller and allowance, and Astra at xhigh as aproposedpin;chooseModelrefuses themblocked, and they have no bounds, protocol or policy. They become tasks with that route's audit and transport (increment 7), which must establish how the effort is set; until thengpt-6-astrais no tier candidate. - Efficient tasks wait for their own measurement. Triage, search and the draft pack pin
claude-sonnet-5low (decision 1's default) as hypotheses, andchooseModelrefuses eachnot-measureduntil its own comparison with Opus 5.5 low on its own corpus (spec L3) makes its basismeasured; qualifying the model for one task never makes another selectable. Anefficienttask's basis is part of its policy digest; afrontiertask's gates nothing and is not, so the six earlier tasks' digests are byte-identical before and after (recorded in the receipt). - The research profile. Search is pinned to
claude-cli-research, the Claude CLI under the separate research profile (spec §8 T1; the spec'sClaudeResearchProfile), on which only Sonnet 5 is listed,unavailable, with the CLI's--effortvalues and anunknownrequest count. Only a drafter may pin it, and qualifying the tool-free route never qualifies it. - Per-call input bounds. Spec §5's are enforced, not only stated: a triage call at most 90 000 bytes (20 Captures of at most 4 KiB) and a synthesis call at most 120 000 (its KB read as 1 000 bytes); the other lifecycle tasks stay at the transport ceilings until their protocols are specified.
discovery-window. A fourth allowance, debited by exactly the discovery occurrences' tasks: triage, search and synthesis, and the blocked audit and challenge.codex-cli, declared unqualified. Decision 3's default: OpenAI is re-pointed fromopenai-responsestocodex-cli, andgpt-6-astrais listed only there,unqualified, with no established effort and anunknownrequest count. The accepted OpenAI Responses adapter (src/openai-producer.ts) is unchanged and stays accepted, but no model is listed on its transport, so nothing pins it. This changes which registries load, so it is a behaviour change of this module.- The README stays within its accepted 25 lines. It keeps the accepted and step-planned rows; the lifecycle's other planned declarations render in the table below, and a test holds that every declaration renders in exactly one of the two. (Since the merge with
main, the step-planned rows render here too.) - Choices the spec leaves open, taken here as proposals. Every lifecycle task is phase
plan, since step 1's phases have none after integration and the growth and retirement drafts serve an owner or Planning decision; the audit, challenge and pack review are drafters, since a verifier presumes a Candidate (spec §6 says so of the challenge); the draft pack and its review debit the released Slice's allowance and the retirement case the improvement window, as weekly defrag does; onlyplanningnames a file (planning/opportunity-gate.ts, spec §2). - Not established. Increment 0 built no lifecycle module (increment 1 builds the schedule: below); every pin, allowance and role is the spec's default for an owner decision that is still open (lifecycle §11, decisions 1–4 and 6): a proposal, not a signed commission.
codex-cliis only declared: no Producers transport, pinned audit (spec increment 7) or commission exists, and neither thatcodex exec -m gpt-6-astra --output-schemaruns headless on the ChatGPT sign-in (A4) nor how that route would set an effort is established. Sonnet 5 is not bound by the Claude transport (CLAUDE_MODELonly; spec increment 6), and eachefficientpin is an unmeasured hypothesis. The research profile has no argv, audit or qualification (spec increment 10).discovery-windowis a name the registry accepts; the registry keeps no ledger, ceiling or debit (the schedule's ledger lands in increment 1, below). - Planned. Each lifecycle module lands in its own increment, moving its declaration into
MODULE_INTELLIGENCEwith the files that land;codex-cligets a Producers transport, audit and commission in increment 7, when the three blocked Astra tasks become tasks; Sonnet 5 a reviewed Claude transport extension and qualification in increment 6; the research profile its own audit and qualification in increment 10.
Lifecycle increment 1
Increment 1 of the proposed lifecycle (§9, row 1), on branch factory/lifecycle-schedule, lands lifecycle-schedule. Its declaration moves, unchanged (none, the same reason), from LIFECYCLE_INTELLIGENCE into MODULE_INTELLIGENCE with its one file, lifecycle-schedule.ts, as the planned rule says; it now renders in the README table and no longer below. No task, pin, allowance or selector rule changes, so no task's policy digest changes. The lifecycle test holds that a landed module names exactly the files that landed and that the other seven have not landed. It passed independent review with the module (Astra round 4 PASS, <local evidence archive>).
Planned declarations
Planned declarations render here rather than in the README, so that its section stays within the accepted 25 lines (merge with main). The handover steps' planned modules (PLANNED_INTELLIGENCE), as renderPlannedIntelligenceTable() renders them:
| Module | Intelligence | Why |
|---|---|---|
| delivery (planned) | none | Attempt settlement, composition and CLI move exact bytes and facts; the only model they drive is the producer's. |
| brief (planned) | optional — brief/explain-trade-offs via frontier/low | Pins and application rules are copied verbatim from the guidance snapshot; a trade-off explanation may be drafted as a labelled proposal the owner accepts. |
| planning (planned) | optional — planning/propose-improvement via frontier/medium; planning/challenge-opportunity blocked, proposed frontier/xhigh | Health, Assessments and admission are rules over independent facts; a model may draft improvement proposals, and challenge an Opportunity only to lower it. |
Lifecycle declarations
The proposed lifecycle's planned modules other than planning (above) and those already built (whose rows are in the README table), as renderLifecycleIntelligenceTable() renders them:
| Module | Intelligence | Why |
|---|---|---|
| signal-intake (planned) | optional — signal-intake/triage via efficient/low; signal-intake/search via efficient/low; signal-intake/audit blocked, proposed frontier/xhigh | Triage and the weekly search are bulk classification an efficient model may do; code verifies every quote span, and an Astra audit is to measure the tier. |
| opportunity-synthesis (planned) | optional — opportunity-synthesis/propose via frontier/high | Composing falsifiable needs from crossed Clusters changes a decision: a frontier model drafts at most five a week, only when one crosses, and rules decide. |
| owner-decisions (planned) | none | The weekly bundle, typed answers and single-use Action grants render from verified records; a model would put an unsourced voice between owner and facts. |
| growth (planned) | optional — growth/draft-pack via efficient/low; growth/review-pack blocked, proposed frontier/xhigh | Prose is where a model helps: an efficient model drafts release copy, code renders every claim from typed facts, and Astra is to review the wording. |
| outcome-validation (planned) | none | Measurement contracts, A/B designs and read-outs are statistics over frozen contracts; a model would be a correlated second judge of the Assessment. |
| capability-retirement (planned) | optional — capability-retirement/draft-case via frontier/high | Detection is rules over usage and health; weighing consumers and migration for an E26 retirement case is judgement a frontier model may draft. |
Lifecycle increment 2
Increment 2 of the proposed lifecycle (§9, row 2), on branch factory/lifecycle-sources from 217887b: the Discovery sources collector landed, so its declaration moved from LIFECYCLE_INTELLIGENCE into MODULE_INTELLIGENCE, as the registry requires of any module whose files exist. Implemented by Opus 5.5; passed independent review with that module (Astra round 5 PASS, after rounds 1–4 FAIL, every finding fixed; <local evidence archive>).
- What changed.
discovery-sourcesnames its two files,discovery-sources.tsanddiscovery-text.ts; its decision (none), reason and empty task list are unchanged. Its row therefore renders in the README table, not the lifecycle table above, anddocs/agents/discovery-sources.mdcarries its one intelligence line. - Accepted is claimed only on review.
MODULE_INTELLIGENCEcan hold a module in review, so its doc comment says "every module whose code exists (accepted, or in review with its increment)", and the README paragraph says the table holds the modules whose code exists; while the increment was in review it also saiddiscovery-sourceswas in review, not accepted (an adversarial finding: the row read as accepted), and since round 5's PASS it says the module was accepted with its increment. Comment and prose only: no line moves. - Nothing else moves. Every task's policy digest is byte-identical before and after (logs
policy-digests-before.json,policy-digests-after.jsonandpolicy-digests-compare.txtunder<local evidence archive>), and every task still refuses. - Line numbers kept. lifecycle cites
src/intelligence.tsby line number, and a test holds each anchor. The entry is therefore a hoisted function,discoverySourcesDeclaration(), placed after those lines and called from the end ofMODULE_INTELLIGENCE, so no line above them moves. - The README's 25 lines. The new row would make the section 26 lines; the blank line between its heading and paragraph is dropped instead, as
maindid forrepository-collector, so the section is 25 lines and the cap is unchanged. Merging withmain, which also added a row, will need a reviewed decision on where the rows go.
Merge with main
Lifecycle increments 0–2 (branches factory/lifecycle-schedule and factory/lifecycle-sources, which both contain increment 0) merged with main at 66f66d3 on branch factory/lifecycle-merge, which had meanwhile added repository-collector (step 1, increment 3). Implemented by Opus 5.5; independent review: <local evidence archive>, where the highest-numbered report holds the current verdict. Receipt: lifecycle-merge-receipt.json.
- Where the rows go. With
repository-collector,lifecycle-scheduleanddiscovery-sources, 19 declarations have code and 3 are step-planned: 22 rows, which with the heading, paragraph, blank line and table header make 27 lines, over the accepted 25. The README therefore keeps exactly the declarations whose code exists (renderIntelligenceTable(), 24 lines), and the step-planned ones render in the planned declarations above (renderPlannedIntelligenceTable(), new), beside the lifecycle's. The 25-line cap, the exact rendered tables and the rule that every declaration renders exactly once across the tables are unchanged; the README test now names three tables instead of two.discovery-sourceskeeps its README row, which its adversarial test requires. - Registry unchanged. No declaration, task, pin, allowance or selector rule changes:
MODULE_INTELLIGENCEismain's withlifecycle-scheduleanddiscovery-sourcesadded (in that order, afterintelligence), andLIFECYCLE_INTELLIGENCEhas neither. Every task's policy digest is byte-identical to each parent's (logs under<local evidence archive>). - Line numbers.
main'srepository-collectorentry and increment 1'slifecycle-scheduleentry each add seven lines above theimplicit-retriesrefusal, so lifecycle now citessrc/intelligence.ts:709(695 in the spec) and its anchor test follows it; the new renderer sits below that line.
Merge with service access
Service access increment 1 (branch factory/service-access, from main at 985b5f9; provisionally accepted — Fable 5.1 (max) round 1, Astra review pending) merged with main at 21bb146, after lifecycle increments 0–2, on branch factory/service-access-merge. Implemented by Opus 5.5; independent review of the merge is pending (an interim Fable 5.1 review under the owner's decision, then Astra's from 3 October 2026; reports under <local evidence archive>). Receipt: service-access-merge-receipt.json.
- One declaration added.
service-access(none, no tasks, namingservice-access.ts,service-discovery.tsandservice-kinds.ts) joinsMODULE_INTELLIGENCEafterdiscovery-sources, with the branch's decision and reason unchanged. No other declaration, task, pin, allowance or selector rule changes, and every task's policy digest is byte-identical tomain's (logs under the directory above). - Line numbers kept. The branch wrote the entry inline, which would add seven lines above the
implicit-retriesrefusal that lifecycle cites assrc/intelligence.ts:709. LikediscoverySourcesDeclaration(), it is therefore a hoisted function,serviceAccessDeclaration(), called from the end ofMODULE_INTELLIGENCE, so no line above the anchor moves and the anchor test is unchanged. - The README's 25 lines. The new row makes the section exactly 25 lines (20 rows), at the cap, and its paragraph names the module as provisionally accepted, so the row does not read as accepted. The next module to land would exceed the cap and needs a reviewed decision on where its row goes, as the lifecycle merge had.
