Skip to content

decisions --sync reports "Synced" and purges an approved decision it wrote nowhere #509

Description

@laurentftech

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

  1. 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 [].)
  2. Approve it: openlore decisions --approve <id>
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions