Cross-project addressing — qualified wikilinks, a project roster, and planning roots
State: open/proposed
Summary
Section titled “Summary”- A task in another repo is addressed as
<slug>:T-XXXX. The qualified form is accepted only ontask.depends_onand, provisionally,milestone.tasks. Every other link stays local. sdlc.yamldeclares the project’s ownslug:and aprojects:roster of peers. The roster is the source of truth for which slugs exist and which repo each names. There is no project entity; a control-plane view of projects is a projection of the roster.planning_roots:in the one rootsdlc.yamldeclares the control units inside a repo. Each unit carries its own corpus of any kind. Definition kinds inherit down the containment tree; a unit adds, never redefines. Work kinds never inherit. The module-graph rules are in D-XCXA-module-graph.- Ids resolve flat across roots; everything else resolves nearest-first.
A duplicate basename across roots is an audit error. A slug-only or
ambiguous target resolves from the linking entity’s root up its ancestors.
slug:IDresolves through the roster. - Audit gains a third link state,
unverified, for a qualified target whose peer is not present on this machine. It is never reported as unresolved and never fails the run unless--strict-peersis passed. - The engine needs no change. One engine root coordinates every repo; the work-order source namespaces ids by slug and rewrites qualified links so the engine sees one id space.
Context
Section titled “Context”Cross-repo work exists today only as spawn-task-pr --target-repo. A task’s
depends_on is a same-corpus wikilink. repos:, paths:, and checkout:
locate checkouts but say nothing about which planning corpus lives where.
There is one planning root per repo, though the ontological control plane
already plans to read three units in this repo.
D-VSLI-distributed-work-runner-architecture leaves two open questions
this decision answers: the addressing registry for peer projects, and
federated planning corpora inside a monorepo with cross-corpus id uniqueness
and wikilink resolution. D-3PDM-cross-project-data-model-adoption’s
activation rule means a new field or entity needs a named producer and a
named consumer before it ships.
Base-36 ids (D-0002-entity-identifier-shape) are unique within a prefix
inside one corpus. Two repos can mint the same T-XXXX. Nothing today
distinguishes them.
Decision
Section titled “Decision”Addressing. A wikilink may carry a project qualifier:
<slug>:T-XXXX or <slug>:T-XXXX-<slug-text>. A new pattern beside
the entity wikilink pattern accepts it. The qualified form is valid only on
task.depends_on and milestone.tasks; the milestone.tasks case is
provisional, pending the open question below. parent_key, related, and
every other link stay local. No entity filename carries the qualifier, so
Obsidian resolves a qualified link to nothing and renders it inert rather
than resolving it wrongly.
Roster. sdlc.yaml gains two keys:
slug: devprojects: polish: repo: sksizer/polish role: peer sdlc-defs: repo: sksizer/sdlc-defs role: definition-source docs_url: https://…slug is this project’s name in the qualified form. projects maps each
peer slug to its repo and role. A peer’s checkout is found through the
existing repos:/checkout: resolution; a per-machine override goes in
sdlc.local.yaml as projects.<slug>.path. projects: never implies
execution scope, the same rule D-RSWP-standalone-repo-sweep-tool fixed for
repos:. sdlc project verify-roster checks that each present peer’s own
slug: equals its roster key and reports whether the peer is trusted.
No project entity. The roster is data in config, not an entity, because its consumers resolve links and locate checkouts; they do not author or review project records. If the ontological control plane wants a Project concept, it is a projection of this roster and never the other direction.
Planning roots. The one root sdlc.yaml may declare:
planning_roots: ontological: root: solutions/ontological/docs/planning label: Ontological agent-pants: root: solutions/agent-pants/docs/planning label: Agent Pants scope: solutions/agent-pants members: - solutions/determined/experiences/agent-pants| Key | Meaning | Default |
|---|---|---|
root | The unit’s planning directory. | Required. |
label | Display name. | The key. |
scope | The directory whose modules the unit takes by containment. | The parent of docs/planning when root ends in it, else none. |
members | Extra paths the unit claims, each with everything under it. | None. |
A sub-root holds its own <plural>/ directories. A second sdlc.yaml is
never placed under a sub-root. Every planning root is a control unit and
carries its own corpus of any kind. Definition kinds inherit down the
containment tree: a unit sees its ancestors’ definitions and may add its
own, but never re-declares or overrides an ancestor’s. Work kinds never
inherit. Repo-wide definitions live at the project root because every unit
inherits them. D-XCXA-module-graph holds the membership, claim, and
inheritance rules. Minting sees the union of taken ids across roots.
Generated artifacts (the index, reports) stay at the project root.
sdlc project setup --planning-root <id> scaffolds a sub-root.
Resolution. An id resolves flat across the vault by basename; a
duplicate basename across roots is an audit error. A slug-only or ambiguous
target resolves nearest-first: the linking entity’s root, then each
ancestor root in turn. A qualified slug:ID resolves through the
roster.
Audit. A qualified link resolves against the peer’s local main when
the peer is present. When the peer is absent, the link is unverified, a
distinct state from unresolved. unverified is reported and does not fail
the run, so a runner without the peers checked out stays green; --strict-peers
promotes it to an error where the peers are expected. A cross-root duplicate
basename is an error. A qualified link on any field other than the two allowed is a
validation error.
Engine. The work-order source unions the roster’s present peers,
namespaces ids as <slug>/T-XXXX, and rewrites qualified links so
WorkOrderLink.target stays in one id space. Scope claims carry the slug as
a path prefix, so the scope filter separates repos without a rule. A peer
that is absent or untrusted yields a not-ready order whose reason names the
peer, so the engine’s explain output shows why. One engine root and one
coordination store cover every repo. Outcome reads the peer’s local main;
scan never touches the network.
Qualify the link, not the id. Ids stay as D-0002-entity-identifier-shape fixes them. The qualifier lives on the edge, where the ambiguity is, and only on the two fields that carry cross-project intent. Restricting the fields keeps every other resolver unchanged.
A roster in config, not an entity. The activation rule in
D-3PDM-cross-project-data-model-adoption asks for a producer and a
consumer. The consumers here are link resolution, audit, and the work-order
source. None of them needs a record with a lifecycle, and git already owns
project identity. A config key is the smaller thing that satisfies the rule.
One root file for planning roots. Project root resolution walks up to
the nearest sdlc.yaml. A second file under a sub-root would re-root every
command run from there. Declaring roots in one place keeps resolution
unambiguous.
Flat resolution for ids, nearest-first for the rest. Flat resolution matches how Obsidian resolves a vault, which is the reading surface for the corpus. A duplicate basename is then a real error, not a namespacing question. Slugs can repeat across units, so a target that names no id needs a tie-break; the nearest ancestor is the one inheritance already sees.
unverified is not unresolved. An absent peer is a fact about this
machine. Reporting it as a broken link would make audit fail on every
checkout that lacks a peer.
Zero engine changes. D-VSLI-distributed-work-runner-architecture makes the engine domain-blind. Namespacing ids in the source keeps the engine’s one-id-space contract intact, and one coordination store for all repos is simpler than routing by slug.
Options considered
Section titled “Options considered”Option A: Slug inside the id
Section titled “Option A: Slug inside the id”Mint ids as dev:T-XXXX everywhere. Rejected: it changes every filename and
every existing link for a concern that only two fields have.
Option B: A project entity
Section titled “Option B: A project entity”Add PJ-XXXX records with repo, role, and status. Rejected for now: no
producer or consumer needs the record, and D-3PDM-cross-project-data-model-adoption
says that shape accumulates zero instances. Revisit if the control plane
needs to author project records.
Option C: A second sdlc.yaml per planning root
Section titled “Option C: A second sdlc.yaml per planning root”Let each unit carry its own config. Rejected: root resolution becomes ambiguous, and inheritance needs one file that names the whole tree.
Option D: Per-repo coordination stores for the engine
Section titled “Option D: Per-repo coordination stores for the engine”Route coordination by slug to each repo’s own ref namespace. Rejected: one engine root with one store gives cross-repo single activation for free. Remote authority sync for the engine’s store is a later question.
Consequences
Section titled “Consequences”Easier. Cross-repo dependencies become explicit and auditable. The
control plane reads planning units from config instead of a hard-coded list.
Multi-repo dispatch is a source change, not an engine change. The same
qualified form later addresses an npm-consumed definition corpus, rendered
as an external link through the roster’s docs_url.
Binds future work. Every new link-bearing field decides whether it
accepts the qualified form; the default is no. A peer that renames its
slug: breaks every qualified link to it; verify-roster is the check.
Breaks nothing today. No existing corpus carries a qualified link or a second planning root.
Migration
Section titled “Migration”| Phase | Change | Needs |
|---|---|---|
| P0 | slug:, projects:, planning_roots: in the config schema; verify-roster; conventions doc | the verbs cascade work to land first, since it rewrites the config schema and its doc |
| P1 | qualified wikilink pattern; resolver; audit unverified; validation of allowed fields | P0 |
| P2 | union corpus loading; flat resolution; duplicate-basename error; union minting; setup --planning-root | P0 |
| P2b | nearest-first resolution; the add-never-redefine audit | P2 and D-XCXA-module-graph’s control units |
| P3 | task next --project/--all; per-repo worktree and trust in the current orchestrator | P1 |
| P4 | sdlc work scan --all-projects; slug-prefixed scope; peer-unresolved reason | P3 and the engine adapter |
| P5 | npm-consumed definition corpus as a definition-source peer | D-ME8T-definition-and-execution-packages |
Open questions
Section titled “Open questions”- Whether
milestone.tasksshould accept qualified links at all, or whether a cross-repo milestone is better expressed as a milestone in each repo withdepends_onbetween their tasks. - Whether the control plane’s Project concept stays a projection or earns an entity.
- How a peer’s
slug:rename is handled beyond detection.