.orgsys composition shipped (preview-only, v0.1.33) — and the adversarial holes it had
What shipped
Section titled “What shipped”Phase 1 composition for the Taxonomy Workbench, v0.1.33. A .orgsys definition can now compose other systems:
mounts: - snap: jd at: Entity/*/Output # one anchor per entity → JD numbering inside eachmounts: [{ snap, at, rebind?, disable? }]— snap a registered system onto an anchor.at:resolves literal paths (one anchor) or*-globs against the vault folder list (one anchor per match), reusing anchored-instance logic.extendsinherits axes/defaults from a base;rebind/disableadjust a mounted system’s slots.- A mounted system’s tags get the host’s namespace via
scopeRule’stagScope— JD underEntity/Cybersader/Outputemits#--cybersader/01-projects, matching the ruleseacow-templates.jsonhand-writes today.
Preview-only: composition is shown in OrgsysPreviewModal (the “Load composed example” preset). It is NOT yet installed into settings or synced — that scoping is load-bearing (see deferred gaps).
The adversarial pass earned its keep (again)
Section titled “The adversarial pass earned its keep (again)”The implement agent’s golden test passed — but it only mounted JD (template-shaped) on a clean vault. Two independent adversaries found that composition would break silently on real vaults:
- Critical — glob bypassed emoji/JD normalization.
resolveMountAnchorsmatched the raw path, soEntity/*/Outputsilently missedEntity/Cybersader/📁 01 - Output→ the whole mounted sub-system vanished with no error. The plugin’s own audience uses decorated folders. Fixed: per-segment match throughfolderNormalize(normalizeSegments), returning the raw path as the anchor; tag-scope also normalizes the host path so the namespace isn’t garbled. - Critical — literal/enumerative mounts doubled tags. Mounting PARA emitted
#projects/projectsand broke inverse, becausescopeRulereplaced the bucket entry point. Fixed:ScopeOptions.entryPointMode: 'replace' | 'prepend'(defaultreplacekeeps the 19 existing scope tests green; composition usesprepend). - Critical — mount cycles stack-overflowed. No visited-guard. Fixed: a visited-set threaded along the compile path; cycles skip + warn.
- Plus: anchor/id dedup, group stamp now
host@snap@anchor, multi-levelextends, and awarnings[]channel surfaced in the modal.
Verified: golden test, an independent re-run of all three critical cases (decorated-vault mount, PARA round-trip, cycle), 995 unit tests, and a real-Obsidian E2E (the composed example expands the mount in-app).
DEFERRED — known gaps for the “install composition into sync” phase
Section titled “DEFERRED — known gaps for the “install composition into sync” phase”These do NOT bite in preview-only mode but MUST be solved before composed rules are installed/synced (documented in compileSystemDef.ts’s header):
- H1 (high) — deeper-wins isn’t persisted. Nested rules out-rank the host only because the preview passes
composedGroupPrecedence(pack). The precedence uses dynamically-named groups (host@snap@anchor) that aren’t carried with the rules; the live sync engines readsettings.groupPrecedence, which won’t contain them. Before install: bake precedence into rule priorities, or persist the groups. Without this, a synced composed pack lets the shallow host rule wrongly win. - M1 — two entities that kebab to the same namespace (
Cyber Sader/Cyber-Sader) collide forward + mis-file inverse. - M6 — a glob whose last
*doesn’t align with a host slot → empty namespace (now warned). - Parser flow-mapping
{a: b}is silently dropped (only flow sequences[...]parse);rebind: ""; snapped tag face without#.
Lesson
Section titled “Lesson”A golden test on the canonical happy path is necessary but not sufficient — it proved JD-on-clean-vault and would have shipped a feature that silently corrupts on decorated and literal-system vaults. The two-adversary “try to break it” pass is what made composition actually correct. Keep it on every non-trivial engine feature.