Summary
openlore decisions --sync silently loses an approved decision when the decision resolves to no spec domain and its scope is not ADR-eligible (component or local). The sync writes no spec and no ADR, still marks the decision synced, purges it from .openlore/decisions/pending.json, and prints ✔ Synced [<id>] <title> with no target path. Only the ledger keeps the id and title; the rationale and consequences are gone.
Reproduction
- Record a decision whose
affectedFiles map to no spec domain. Keep the default or component scope:
openlore decisions record --title "Some component decision" \
--rationale "Why" --files src/pi/extension.ts --scope component
(src/pi/extension.ts is not listed under any spec domain here, so affectedDomains is [].)
- Approve it:
openlore decisions --approve <id>
- Sync it:
openlore decisions --sync
Observed
[scan] Syncing 1 approved decision(s)...
[ok] ✔ Synced [848b360d] Pi starts with the substrate tool surface and on-demand tool groups
- No spec file or ADR changes (
git status is clean for openspec/).
openlore decisions status <id> returns found: false.
.openlore/decisions/ledger.jsonl has draft and approved entries, but no synced entry.
Expected
The decision must not be marked synced or purged when the sync wrote it nowhere. The CLI should report that no target exists, keep the decision approved in the store, and exit non-zero (like a partial sync).
Root cause (origin/main 9971c659)
syncer.ts#L183: the spec loop iterates decision.affectedDomains; with [], resolved is empty and owner is undefined.
syncer.ts#L215: the "no owner and not ADR-eligible" case is refused only when decision.constraints is set. A plain decision falls through.
syncer.ts#L248: no ADR is created, because ADR_SCOPES holds only cross-domain and system.
syncer.ts#L79-L81: the decision is still pushed as status: 'synced' with syncedToSpecs: [], so purgeInactiveDecisions removes it.
decisions.ts#L1436: the CLI prints ✔ Synced for every entry in result.synced, even with no target path.
Suggested fix
- In the syncer, treat "no owner spec and no ADR" as a per-decision error for every decision, not only constraint-bearing ones. The error should name the cause (no spec domain resolved, scope not ADR-eligible) and the remedies (
--scope cross-domain|system, or --files that map to a spec domain).
- The decision then stays
approved in the store, result.errors is non-empty, and the CLI exits 1. The existing partial-sync path already does this.
- Add a regression test: an approved
component decision with affectedDomains: [] is not purged, and the sync reports an error.
- Optional:
decisions record / record_decision could warn at record time when affectedFiles resolve to no domain and the scope is not ADR-eligible.
Workaround
Record the decision with --scope cross-domain (or system), so the sync writes an ADR under openspec/decisions/.
Found while syncing ADR-0034 for #505 / #508.
Summary
openlore decisions --syncsilently loses an approved decision when the decision resolves to no spec domain and its scope is not ADR-eligible (componentorlocal). The sync writes no spec and no ADR, still marks the decisionsynced, purges it from.openlore/decisions/pending.json, and prints✔ Synced [<id>] <title>with no target path. Only the ledger keeps the id and title; the rationale and consequences are gone.Reproduction
affectedFilesmap to no spec domain. Keep the default orcomponentscope:src/pi/extension.tsis not listed under any spec domain here, soaffectedDomainsis[].)openlore decisions --approve <id>openlore decisions --syncObserved
git statusis clean foropenspec/).openlore decisions status <id>returnsfound: false..openlore/decisions/ledger.jsonlhasdraftandapprovedentries, but nosyncedentry.Expected
The decision must not be marked synced or purged when the sync wrote it nowhere. The CLI should report that no target exists, keep the decision
approvedin the store, and exit non-zero (like a partial sync).Root cause (origin/main
9971c659)syncer.ts#L183: the spec loop iteratesdecision.affectedDomains; with[],resolvedis empty andownerisundefined.syncer.ts#L215: the "no owner and not ADR-eligible" case is refused only whendecision.constraintsis set. A plain decision falls through.syncer.ts#L248: no ADR is created, becauseADR_SCOPESholds onlycross-domainandsystem.syncer.ts#L79-L81: the decision is still pushed asstatus: 'synced'withsyncedToSpecs: [], sopurgeInactiveDecisionsremoves it.decisions.ts#L1436: the CLI prints✔ Syncedfor every entry inresult.synced, even with no target path.Suggested fix
--scope cross-domain|system, or--filesthat map to a spec domain).approvedin the store,result.errorsis non-empty, and the CLI exits 1. The existing partial-sync path already does this.componentdecision withaffectedDomains: []is not purged, and the sync reports an error.decisions record/record_decisioncould warn at record time whenaffectedFilesresolve to no domain and the scope is not ADR-eligible.Workaround
Record the decision with
--scope cross-domain(orsystem), so the sync writes an ADR underopenspec/decisions/.Found while syncing ADR-0034 for #505 / #508.