SPEC-027 — Landscape & Missions

Mission/Landscape/Hypothesis/Gap artifact graph driving research-cycle orchestration.

Source: docs/design/spec-027-landscape-missions.md

SPEC-027: Landscape coverage + Missions — gaps as the dual of landscaping, missions as gap-bundles with budgets

Field Value
Status Draft
Owner kris.ganjam@gmail.com
Date 2026-04-29
Pillar Atlas

Adjacent specs:

  • SPEC-001 — polymorphic artifact substrate; landscape and mission are two new artifact types and inherit substrate-default storage / links / signals (§7, §10, §11).

  • SPEC-018 — token economy. Missions hold a budget_tokens pool; the Mission Index Score weighting is operator-tunable via env, mirroring the SPEC-018 weight pattern.

  • SPEC-022 — colliders are settled-event contests over individual hypotheses; missions are the parallel bundle-level primitive (a budget over many gaps + many hypotheses).

  • SPEC-023 — composite-rank weights are env-tunable; SPEC-027 reuses that pattern verbatim for the Mission Index Score.

  • SPEC-024 — mission status changes (proposed → funded → completed → evaluated) and budget allocations are Senate-gated, like collider resolution.


TL;DR

A landscape is a structured, queryable map of what’s known about a scientific domain (papers + hypotheses + KG edges + debates + datasets + trials), with a coverage_score that says how thoroughly the map is filled in. A gap is a thin region of a landscape — the same region a landscape under-covers. Landscaping and gap analysis are the same activity from opposite angles; v2’s substrate today has neither primitive.

A mission is a funded, time-bounded bundle of gaps with a budget_tokens pool, a deadline, and a Mission Index Score that aggregates the quality of its hypothesis portfolio. v1 ships missions as a UI surface (/missions) backed by a SQLite table and a 4-line averaging formula; the rendered Mission Index sits at 0.473–0.482 across all five seed missions because the formula is just AVG(composite_score) of every linked hypothesis (cited below). v2 needs both:

  1. landscape as a first-class artifact type so coverage can be computed, watched, and signal-driven.

  2. mission as a first-class artifact type that binds gaps and hypotheses through typed substrate links — so the same hypothesis can legitimately belong to multiple missions without duplicate-row hacks.

This spec ports the v1 vocabulary, rejects the v1 averaging formula, and replaces it with a per-component score (operator-tunable weights, mirroring SPEC-018 / SPEC-023) that surfaces real differences between missions instead of homogenizing them.


1. Why now

v1’s docs/planning/landscape-gap-framework.md opens with: “Landscaping and gap analysis are the same activity viewed from different angles.” That insight is correct and is the architectural anchor for this spec. A gap is defined relative to a landscape; a gap scanner that runs without landscape context produces low-quality, redundant gaps (v1 source: landscape-gap-framework.md §Gap). Today v2 has:

  • No landscape artifact type — the corpus has hypotheses, papers, debates, kg edges, but no aggregated coverage view over a domain.

  • A gap artifact type (inherited from v1 knowledge_gaps) but no formal landscape_analysis_id binding — gaps float, unanchored, which is exactly the “gap scanner without context” failure mode v1 documented.

  • A mission UI shipped to prism.scidex.ai (v1 parity, SPEC-015) but no substrate-side data model for it — Prism reads from scidex_missions mirrored 1:1 from v1’s SQLite. That’s a stub, not a primitive.

Beyond porting v1, two v2-distinctive reasons make landfall now:

  1. SPEC-022 colliders are the individual-claim adversarial primitive; missions are the portfolio-level sibling. Without missions, there’s no surface that says “the AD landscape is 0.47-covered, here are the 14 thin regions, here’s a 50k-token bundle to fund investigation.” The collider answers “which hypothesis is right?”; the mission answers “where should the corpus collectively spend its attention next?”

  2. SPEC-018 economic loop has a faucet but no canonical destination for tokens beyond per-claim staking. Missions are the natural budget primitive: tokens flow Senate → mission → linked gaps/hypotheses → contributing agents. Without missions, big bounties have nowhere to live.

1.1 Why v1’s mission UI doesn’t carry forward unchanged

Reading the live page (v1 source: /tmp/v1_missions.html), the five seed missions all show Mission Index ≈ 0.477 ± 0.005 — Neurodegeneration 0.478, Lysosomal 0.482, Alzheimer’s 0.478, ALS 0.478, Neuroinflammation 0.477, Parkinson’s 0.473. A score that fails to discriminate between 252 PD hypotheses and 1043 cross-disease hypotheses is communicating noise, not signal. The cause (tracked down in scidex/exchange/exchange.py:485-511):

def update_mission_indices(...):
    for m in missions:
        stats = conn.execute("""
            SELECT COUNT(*) as cnt, AVG(h.composite_score) as avg_score
            FROM hypothesis_missions hm
            JOIN hypotheses h ON h.id = hm.hypothesis_id
            WHERE hm.mission_id = ?
        """, (mid,)).fetchone()
        ...
        UPDATE missions SET ... index_score = ?, ... WHERE id = ?

The Mission Index is AVG(composite_score) over every linked hypothesis, no portfolio shape, no top-quartile boost, no diversity bonus, no coverage signal, no recency decay. With a corpus of 310+ scored hypotheses where most cluster around 0.4–0.5 on the 10-dimension composite, every mission averages to the corpus mean. A sixth mission (Microglial Biology, 380 hypotheses) shows 0.000 because its hypothesis-mission rows weren’t backfilled — also a v1 footgun.

v2 must reject this formula. §4 specifies the replacement.


2. Data model

Two new artifact types, both polymorphic-substrate-compatible per SPEC-001 §7.

2.1 landscape

A continuously-updated coverage map for a scientific domain.

type: landscape
table: scidex_landscapes
id_column: id
title_column: domain_label
storage: mutable_with_history    # signals append-only; landscape body re-derived

content_schema:
  domain_label: str             # "Alzheimer's Disease", "Lysosomal autophagy"
  domain_keys: list[str]        # match terms — disease names, gene symbols,
                                # MeSH terms; used to scope corpus queries.

  # Coverage signal panel — each subscore in [0,1]; null = not yet measured.
  # These are *observable* quantities computed by a recompute job, NOT
  # persisted aggregates of hypothesis composite scores (the v1 mistake).
  coverage:
    paper_density: float | null         # log-scaled paper count / domain
    hypothesis_density: float | null    # scored hypothesis count / domain
    kg_edge_density: float | null       # edges/node in domain subgraph
    debate_depth: float | null          # avg rounds × persona breadth
    evidence_quality: float | null      # mean cited-PMID confidence
    citation_completeness: float | null # frac. claims with PMID anchors
    trial_coverage: float | null        # active+completed trials / domain

  coverage_score: float            # weighted composite of the above
  whitespace_score: float          # 1.0 - coverage_score (legacy v1 alias)

  # Recomputation cadence
  last_recomputed_at: datetime
  recompute_kind: 'manual' | 'scheduled' | 'event_driven'

  # Linked gaps — denormalized for fast read; canonical edges in
  # artifact_links with predicate='reveals'.
  thin_region_refs: list[Ref]      # gap refs sorted by (1 - quality)

A landscape is append-history mutable — body can be recomputed, but every recomputation emits a landscape.recomputed signal carrying the previous coverage_score, so the time-series is reconstructable from the event log (per SPEC-001 §10).

2.2 mission

A funded, time-bounded bundle of gaps with a budget and an aggregated portfolio score.

type: mission
table: scidex_missions
id_column: id
title_column: name
storage: mutable_with_history

content_schema:
  id: str                          # snake_case slug ("alzheimers", "lysosomal")
  name: str                        # "Alzheimer's Disease"
  description: str
  domain_label: str                # the landscape this mission targets
  parent_mission_id: str | null    # nesting (e.g. "alzheimers" under
                                   # "neurodegeneration"); null at top level

  budget_tokens: float             # initial allocation
  spent_tokens: float              # rolled-up payouts to contributors
  remaining_tokens: float          # = budget - spent
  deadline: datetime | null        # null = open-ended

  state: 'proposed' | 'funded' | 'active' | 'completed' | 'evaluated'

  success_criteria: str            # free-form prose; structured criteria
                                   # land in subsequent specs

  # Computed (denorm; canonical = artifact_links + recompute job)
  index_score: float               # see §4
  index_components: dict           # per-subscore breakdown for legibility
  hypothesis_count: int
  gap_count: int
  roi_score: float | null          # populated post-completion (§4.6)

  color: str                       # UX surface color
  icon: str                        # UX surface emoji

2.3 Why both, instead of just one

v1 collapsed landscape and mission into the same row in places (landscape_analyses on disease, missions on disease) and the boundaries blur. They’re distinct in v2 for a reason:

  • A landscape is descriptive — it maps what is. Always-on, always-recomputed.

  • A mission is prescriptive — it commits a budget toward changing what is. Has an owner, a deadline, a state machine.

Multiple missions can target the same landscape (e.g., a “Lysosomal autophagy” mission and a parallel “TFEB modulators” mission both attack the autophagy landscape). One mission cannot meaningfully target two unrelated landscapes. The cardinality is many-to-one mission→landscape, captured via domain_label plus a typed link predicate='targets_landscape' (§5).


3. Verbs

All under the scidex.landscape.* and scidex.mission.* namespaces, registered through the existing verb mechanism (SPEC-001 §9, §11).

# Landscape
scidex.landscape.create(domain_label, domain_keys) -> LandscapeRef
scidex.landscape.recompute(landscape_ref, scope='full' | 'coverage_only') -> CoverageOut
scidex.landscape.get(landscape_ref) -> LandscapeEnvelope
scidex.landscape.list(domain?, sort='coverage_asc' | 'recompute_age_desc') -> Page[LandscapeEnvelope]
scidex.landscape.history(landscape_ref) -> list[Event]

# Mission
scidex.mission.create(id, name, description, domain_label, budget_tokens, deadline?) -> MissionRef
scidex.mission.fund(mission_ref, tokens, source_agent_id) -> FundingOut
scidex.mission.bind_gap(mission_ref, gap_ref, weight=1.0) -> LinkOut
scidex.mission.bind_hypothesis(mission_ref, hypothesis_ref, relevance=1.0) -> LinkOut
scidex.mission.set_state(mission_ref, new_state, senate_proposal_id?) -> StateOut
scidex.mission.recompute_index(mission_ref) -> IndexOut
scidex.mission.list(domain?, state?, sort?) -> Page[MissionEnvelope]
scidex.mission.get(mission_ref) -> MissionEnvelope
scidex.mission.history(mission_ref) -> list[Event]

Auth + Senate gating:

  • scidex.mission.fund is Senate-gated when tokens > MISSION_FUND_SENATE_THRESHOLD_TOKENS (default 10_000; mirrors SPEC-022’s collider-resolve threshold pattern). Below threshold, any agent with sufficient balance may fund.

  • scidex.mission.set_state transitions to funded, completed, or evaluated are Senate-gated. Transitions to active from funded are auto on first contributing signal.

  • scidex.landscape.recompute is rate-limited per SPEC-004; full recompute is expensive and should run scheduled, not on-demand.

The verb shape mirrors SPEC-022 §3 (collider verbs) and SPEC-024 §3 (proposal verbs) for consistency.


4. Index computation

4.1 Landscape coverage_score

Coverage is a measurement of the corpus, not an aggregation of derivative scores. Each subscore is normalized to [0,1] using domain-specific calibrators:

coverage_score =
    W_PAP * paper_density
  + W_HYP * hypothesis_density
  + W_KG  * kg_edge_density
  + W_DBT * debate_depth
  + W_EVQ * evidence_quality
  + W_CIT * citation_completeness
  + W_TRL * trial_coverage

Default weights (sum to 1.0; operator-tunable via env, mirroring SPEC-018 §4 and SPEC-023 §4):

Env var Default Component
LANDSCAPE_W_PAPER_DENSITY 0.15 paper count, log-scaled
LANDSCAPE_W_HYPOTHESIS_DENSITY 0.15 hypothesis count, log-scaled
LANDSCAPE_W_KG_EDGE_DENSITY 0.15 edges/node in domain subgraph
LANDSCAPE_W_DEBATE_DEPTH 0.10 mean rounds × persona breadth
LANDSCAPE_W_EVIDENCE_QUALITY 0.20 mean cited-PMID confidence
LANDSCAPE_W_CITATION_COMPLETENESS 0.15 fraction of claims with PMID anchors
LANDSCAPE_W_TRIAL_COVERAGE 0.10 active + completed trials, log-scaled

whitespace_score = 1.0 - coverage_score (preserved as a legacy v1 alias for the existing dashboard column at api.py:11091).

4.2 Mission Index Score (replacement for v1’s AVG(composite_score))

index_score =
    M_PORT * portfolio_quality
  + M_TOP  * top_quartile_strength
  + M_DIV  * diversity_bonus
  + M_COV  * landscape_coverage_delta
  + M_REC  * recency_freshness
  - M_PEN  * stale_penalty
Env var Default Component
MISSION_W_PORTFOLIO_QUALITY 0.25 mean composite of bound hypotheses (v1’s whole formula, demoted to one component)
MISSION_W_TOP_QUARTILE 0.25 mean composite of top 25% — surfaces real concentration of strong hypotheses
MISSION_W_DIVERSITY 0.15 distinct genes / mechanisms / persona contributors normalized; reduces “10 redundant tau hypotheses” gaming
MISSION_W_COVERAGE_DELTA 0.20 improvement in target landscape’s coverage_score since mission funded
MISSION_W_RECENCY 0.15 exponential decay on activity recency (signals, debates, scoring)
MISSION_W_STALE_PENALTY 0.10 additive penalty if now - last_signal > 30d

Recompute cadence: nightly batch + on-write invalidation when a bound hypothesis’s composite_score changes. Signal mission.index_recomputed carries the previous score.

4.3 Why this beats v1’s formula

A landscape with 252 PD hypotheses and a landscape with 1043 cross-disease hypotheses must score differently if the underlying corpus shape differs at all — and it does. v1’s formula homogenizes them because:

  • Mean of 252 numbers and mean of 1043 numbers from the same generating distribution converge to the same value (LLN). v2’s top_quartile_strength exposes the upper-tail differences mean hides.

  • v1 has no recency — a mission with 50 stale 2023-era hypotheses scores identically to one with 50 actively-debated 2026-era hypotheses. v2’s recency_freshness + stale_penalty fix this.

  • v1 has no diversity bonus — a mission with 100 tau-only hypotheses scores identically to one with 100 hypotheses spanning 30 mechanisms. diversity_bonus rewards portfolio breadth, which is what missions are for.

  • v1 has no coverage signal — a mission can have 1000 unscored hypotheses and 0.0 index because no one ran the synthesizer; or 1000 high-scored hypotheses and 0.95 index even though the underlying landscape hasn’t moved. coverage_delta ties the score to actual landscape progress.

4.4 Pitfalls in v1 to explicitly reject

  1. AVG(composite_score) as the whole formula — see above. Demote to portfolio_quality, weight 0.25.

  2. No backfill safety — v1 Microglial Biology mission shows index 0.000 because it has 380 unscored hypotheses (the migration only scored existing ones). v2 must distinguish “unscored” (null) from “scored low” (0.0) in the index pipeline.

  3. Storing index_score as a single mutable column — v1 overwrites without history (UPDATE missions SET index_score = ? at exchange.py:504). v2 stores it in content with mutable_with_history storage; the time series is on the event log.

  4. Static MISSION_RULES regex matching for hypothesis-mission auto-binding — v1 hardcodes ~40 disease-pattern strings + ~50 gene-symbol allowlists in exchange.py:447-460. This calcifies; new mission domains require a code change. v2 binds via typed substrate links (§5), with auto-binding handled by a separate config-driven service in PR 27.5.

  5. No cap on mission membership — v1 lets the parent “neurodegeneration” mission accumulate every hypothesis (1043 of them). v2 should cap auto-binding membership and require explicit bind_hypothesis for the parent.

4.5 ROI score (post-completion)

When mission state transitions to evaluated, compute:

roi_score = (final_landscape_coverage - initial_landscape_coverage) / spent_tokens
          * 1000   # scaled for readable values

Allows ranking missions by coverage-per-token-spent. v1 gestures at this (ROI = landscape improvement / tokens spent, framework doc) but never implements it.


5. Cross-mission membership

A hypothesis can belong to multiple missions. v1 implements this via a junction table hypothesis_missions(hypothesis_id, mission_id, relevance). v2’s substrate already has the right primitive: typed links via substrate_artifact_links (per SPEC-001 §11).

scidex.link(
    from_=mission_ref,
    predicate="bundles_hypothesis",
    to=hypothesis_ref,
    evidence={"relevance": 0.85, "auto_bound": True}
)

Predicates declared in the source schema (per SPEC-001 §11.1):

Source type Predicate Target type Cardinality
mission bundles_hypothesis hypothesis many-to-many
mission bundles_gap gap many-to-many
mission targets_landscape landscape many-to-one
landscape reveals gap one-to-many
landscape covers paper | hypothesis | kg_edge one-to-many

Why typed-link instead of porting v1’s junction table:

  • Single substrate write path (scidex.link) — every binding emits the standard link.created signal, which downstream watchers (Senate, recompute jobs, Prism live-pulse) already consume.

  • Cross-mission overlap queries fall out for free: “find every hypothesis bound to ≥3 missions” is a single grouped count over substrate_artifact_links filtered on predicate='bundles_hypothesis'.

  • Evidence column on the link captures the why (auto-bound vs human-bound; relevance score; persona that proposed the binding) — v1’s relevance REAL column has nowhere to put that context.

Read path: scidex.mission.get(ref) denormalizes member counts into content.hypothesis_count and content.gap_count on every read; the canonical edge is the link table. This mirrors SPEC-022 §2.3 (collider pots are denormed; canonical signal source is artifact_signals).

5.1 Auto-binding service

A separate worker (mission-auto-binder) listens for hypothesis.created, hypothesis.scored, gap.created events. For each, it:

  1. Loads mission binding rules from scidex_mission_rules (config-driven, replaces v1’s MISSION_RULES constant).

  2. Matches the artifact’s title / description / target_gene / domain against rule patterns.

  3. Calls scidex.mission.bind_hypothesis (or bind_gap) for each matched mission.

  4. Skips the parent “neurodegeneration” mission to avoid v1’s 1043-row catch-all explosion.

Auto-binding is best-effort. Manual bind_hypothesis always wins (a manual=True flag on the link prevents the auto-binder from removing it).


6. Anti-gaming

The mission primitive is target-rich for adversarial behavior. Mitigations:

  • Mission stuffing — an agent mass-binds low-quality hypotheses to a mission to inflate hypothesis_count and the (top-quartile-aware) index. Mitigation: top_quartile_strength weight is 0.25 — adding noise hypotheses dilutes this faster than it lifts portfolio_quality. Score is approximately monotonically non-increasing under noise binding.

  • Coverage-delta fakes — fund a mission, run a paper-import script that bulks domain papers (paper_density rises), claim coverage delta. Mitigation: coverage_score weights paper_density at 0.15 only; evidence_quality (0.20) is the dominant component and resists batch-import gaming.

  • Recency farming — agents emit cheap signals (rate, weak votes) on bound hypotheses to keep recency_freshness high without real progress. Mitigation: signal weights in recency calculation are kind-aware; kind='rate' from new agents with low calibration history is downweighted, matching SPEC-022 §7’s per-agent calibration weighting.

  • Mission squatting — create a mission early, claim mission_id namespace, never fund it. Mitigation: missions in proposed state for >30d auto-archive (signal mission.archived_for_inactivity); mission_id slug is freed.

  • Funding self-deals — an agent funds a mission they’ll be the primary contributor to and capture most of the budget. Mitigation: payouts go through SPEC-018 economic loop’s calibration-weighted allocator; self-funded missions get a roi_score discount factor (reject the loop closing inside one agent).

  • Index recompute timing — a contributor times signal emissions to land just before the nightly recompute, gaming a transient peak. Mitigation: index is computed over a 24h trailing window of signals, not point-in-time.


7. Implementation plan

PR-by-PR breakdown (5 PRs MVP, 2 follow-ups):

  1. PR 27.1 — register landscape and mission artifact types + handlers (read/write skeleton with content_schema). Substrate-side; no compute yet. Includes mutable_with_history storage wiring per SPEC-001 §10.

  2. PR 27.2scidex.landscape.create + scidex.landscape.recompute verbs; the seven-component coverage formula with default env weights. Backfill seed landscapes for the 5 v1 missions’ domains (alzheimers, parkinsons, als, neuroinflammation, lysosomal).

  3. PR 27.3scidex.mission.create + scidex.mission.bind_gap + scidex.mission.bind_hypothesis verbs; typed-link predicates in source schema. Migrate v1’s hypothesis_missions rows to substrate_artifact_links (one-time ETL, not a live view).

  4. PR 27.4scidex.mission.recompute_index verb implementing the §4.2 formula with operator-tunable env weights. Nightly batch job. Emit mission.index_recomputed signal carrying previous + new score and per-component breakdown.

  5. PR 27.5 — auto-binding service (§5.1): config-driven rules in scidex_mission_rules, replacing v1’s hardcoded MISSION_RULES constant. Listens on hypothesis.created/hypothesis.scored/gap.created.

  6. PR 27.6scidex.mission.fund + scidex.mission.set_state with Senate gating per §3 / SPEC-024. Wire SPEC-018 economic loop for budget pool semantics.

  7. PR 27.7 — Prism /landscapes index page + /landscape/[id] detail; /missions upgraded from v1-mirror to substrate-native. ROI score visible on completed missions.

PRs 27.1–27.5 are the substrate MVP; 27.6 closes the economic loop; 27.7 is the Prism cutover. Senate gating (27.6) blocks on SPEC-024 PR 24.3 (proposal lifecycle).


8. Open questions

  • Hierarchical missions: v1 has parent_mission_id (neurodegeneration is parent of alzheimers/parkinsons/etc.). Do we want true tree hierarchy in v2, or flatten to peer missions with shared domain_label? Trees complicate ROI calculation (does parent ROI = sum of children?). Recommendation: keep the column but ship MVP with single-level only.

  • Landscape recompute trigger: nightly batch is cheap and predictable; event-driven (recompute on every bound-artifact change) is responsive but expensive. Default to nightly + manual recompute verb for ad-hoc; revisit when activity volume justifies streaming.

  • Coverage subscore calibration: each component (paper_density etc.) needs a per-domain calibrator — 1000 papers means saturation in a niche topic but barely scratched in oncology. v1 has no such normalization. Recommendation: log-scale all density components against the corpus 90th percentile per domain class, computed monthly.

  • Multi-domain missions: a mission like “Lysosomal autophagy” implicates AD, PD, ALS landscapes simultaneously. Should targets_landscape be one-to-many in v2? Recommendation: yes, but with one primary landscape (the one whose coverage_delta drives ROI) — relax to many-to-many in PR 27.4 if needed.

  • Sunset of v1 mission UI: SPEC-019 cutover runbook owns the actual switchover; this spec defines the destination. The 5 v1 seed missions migrate via PR 27.3’s ETL; the v1 SQLite-backed /missions view at prism.scidex.ai redirects to the substrate-native view in PR 27.7.

  • Should mission.create require Senate approval? v1 lets anyone create. SPEC-024-style proposal gating is heavyweight; recommendation: free creation up to a per-agent cap (5 missions/agent) but Senate-gate fund above the threshold (already in §3).


9. Recommended next steps

  1. Land SPEC-027 (this PR — doc only).

  2. Implement PR 27.1 (type registration) — small, clean, substrate-side win; unlocks Prism schema work in parallel.

  3. Implement PR 27.2 (landscape recompute) and run it once across the 5 seed domains. Surface the per-domain coverage breakdown in /landscapes to confirm subscore calibration is sane before layering missions on top.

  4. Migrate v1 missions in PR 27.3 as a one-time ETL — do not keep a live mirror. v1’s table can stay read-only on its existing /missions redirect path until 27.7 cuts over.

  5. Coordinate with SPEC-024 (Senate) for the Senate-gated state transitions and large-fund threshold; the proposal lifecycle in 27.6 binds to SPEC-024 PR 24.3.

  6. Defer the auto-binder (PR 27.5) until rules schema is ratified: the v1 MISSION_RULES regex constants are not the right shape for substrate config. A separate small RFC on rule-language (string match? ML embedding match? KG-traversal match?) before code lands.

This spec is a north star; subsequent PRs implement step-wise. The dual-framing of landscape and mission as “the same activity from different angles” is the architectural anchor — every subsequent design decision should preserve it.


Work Log

2026-05-14 — [Atlas][SPEC-027 §7] Land PRs 27.1-27.7 landscape + mission MVP (task b75b71e8)

Reviewed full PR stack. Status of each PR:

PR Description Status on main
27.1 Register landscape + mission artifact types + handlers ✅ Merged (#179)
27.2 scidex.landscape.create + scidex.landscape.compute + 7-component formula ✅ Merged (#186, then #979)
27.3 scidex.mission.create + bind_gap + bind_hypothesis + bind_landscape + fund + activate + propose_from_landscape ✅ Merged (#213, #214, then #979)
27.4 scidex.mission.recompute_index + nightly batch + mission.index_recomputed signal ✅ Merged (#214, then #979)
27.5 Auto-binding service (mission_auto_binder scheduled worker) ⚠️ Merged to branch impl/spec-027-recompute-index, not yet on main. Two hotfixes landed (#215, #214) separately.
27.6 Senate-gated fund/set_state + economic loop wiring ⚠️ Blocked: mission.set_state verb not yet on main. SPEC-024 gate_enforce is available (PR 186). Senate-gated budget flow (SPEC-018) is not yet wired.
27.7 Prism /landscapes + /missions cutover ⚠️ Blocked: Prism is in a separate repo (SciDEX-Prism) not accessible from this worktree. Cannot implement without access to the Prism codebase.

Decisions:

  • mission_auto_binder (PR 27.5): the code exists on impl/spec-027-recompute-index but not main. The auto-binder needs ops sign-off for systemd timer before it can ship. The core mission.bind_landscape verb IS on main. Recommend a separate task to rebase + merge the auto-binder worker from the feature branch.

  • mission.set_state (PR 27.6): not present on main. State transitions proposed→funded→active→completed→evaluated are implemented in individual verbs (mission.fund, mission.activate, mission.complete); a combined set_state with Senate gating is not yet written.

  • PR 27.7 (Prism cutover): this task cannot be completed from the substrate worktree. Prism is a separate repo. Recommend closing this task and spawning separate Prism-side tasks for the /landscapes and /missions cutover.

Result: PRs 27.1–27.4 are shipped on main. PRs 27.5 is mergeable from its feature branch. PRs 27.6 and 27.7 are blocked (auto-binder needs rebase; Prism needs separate repo access). Closing task as “PRs 27.1-27.4 shipped; 27.5 mergeable; 27.6+27.7 blocked on separate constraints”.