Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
393e8a4
test(schedule): define durable reason-event persistence contract
seonghobae Aug 15, 2026
f3aa61a
feat(schedule): persist terminal reason events atomically
seonghobae Aug 15, 2026
f527bd7
test(schedule): register reason-event persistence coverage
seonghobae Aug 15, 2026
40d3039
test(schedule): lock persistence coverage registration
seonghobae Aug 15, 2026
bcaa740
docs(schedule): trace durable reason-event persistence
seonghobae Aug 15, 2026
64cc3dc
docs(changelog): record durable schedule reason persistence
seonghobae Aug 16, 2026
8f7e16f
test(ci): reject synthetic PR test checkouts
seonghobae Aug 16, 2026
d313d5a
chore(schedule): remove unrelated workflow contract
seonghobae Aug 16, 2026
4a5e276
merge(schedule): reconcile reason-event persistence with current auth…
seonghobae Aug 16, 2026
482dd26
test(schedule): preserve causal failure when savepoint rollback fails
seonghobae Aug 16, 2026
42d76b4
fix(schedule): fail closed on reason-event savepoint cleanup
seonghobae Aug 16, 2026
c613401
merge(schedule): reconcile reason persistence with current authorizat…
seonghobae Aug 16, 2026
5103c79
merge(schedule): reconcile reason persistence with repaired authoriza…
seonghobae Aug 17, 2026
057d043
merge(schedule): reconcile reason persistence with authorization parent
seonghobae Aug 17, 2026
3922071
fix(stack): reconcile reason persistence with current authority
seonghobae Aug 20, 2026
2360d0c
fix(stack): reconcile reason persistence with current authority
seonghobae Aug 24, 2026
92858d9
fix(stack): reconcile reason persistence with latest authority
seonghobae Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added normalized SQLite persistence for authorized terminal schedule reason
events, verified cancellation approval evidence, immutable audit records, and
same-savepoint resource-version transition/rollback without duplicating the
authoritative work-item store.
- Added a deterministic, framework-neutral schedule-outcome derivation domain
that distinguishes observed facts from versioned early/on-time/late,
in-progress, blocked, skipped, cancelled, and not-performed interpretations;
Expand Down
46 changes: 46 additions & 0 deletions docs/doctoring/schedule-reason-event-persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Schedule reason-event persistence evidence

## Status and scope

This document describes **active pull-request work only**. Protected `develop` does not yet ship this SQLite adapter. The slice is stacked on PR #518 and has been reconciled against exact authorization parent `4b17e7d15c02002bb985e9d7b28fa06e78b4afab`, including the domain-side prohibition on self-approved cancellation.

The slice owns one bounded persistence responsibility: atomically preserve an already-authorized terminal schedule reason event, any verified cancellation approval evidence, an immutable audit record, and the authoritative resource-version transition that makes the write current. It does not add HTTP routes, browser authority, a second work-item source of truth, schedule-variance mathematics, forecasting, or decision UI.

## Persistence contract

`server/schedule_reason_event_sqlite.mjs` installs three normalized relations:

- `schedule_reason_events`: immutable reason facts with tenant/project/work-item identity, prior and committed resource versions, reason vocabulary, actor, trusted timestamps, and authorization identity.
- `schedule_reason_event_approval_records`: optional cancellation approval evidence keyed by event ID.
- `schedule_reason_event_audit_records`: one immutable audit identity/action for the event without duplicating event facts.

Owned database objects use descriptive multiword snake_case names. The adapter deliberately does **not** create or own an authoritative work-item version table. Its injected `advanceResourceVersion` function must update the existing authoritative store synchronously on the same SQLite connection and inside the same savepoint. The version transition, event insert, optional approval insert, and audit insert therefore commit or roll back together.

## Concurrency, rollback, and failure behavior

The repository receives the exact `expectedResourceVersion` already authorized by the domain boundary. A stale transition, missing/blank resulting version, or non-advancing transition fails closed before durable event insertion. It does not retry against a newer resource version because that would broaden authority beyond the decision that was actually checked.

A named SQLite savepoint wraps the version transition and persistence writes. The realistic regression deliberately forces an audit uniqueness failure after a real version update and proves both the version and all child persistence roll back. Cleanup also preserves the causal write error when rollback cleanup itself fails.

## TDD and verification evidence

- RED `393e8a48b4815a0fdbb2104fc3c4d846f65de057` introduced the persistence behavior contract before the production module and failed with `ERR_MODULE_NOT_FOUND`.
- GREEN `f3aa61ae6cd0caa774386be3a8e3959ab210501f` added the adapter.
- `f527bd778a2ae19f3c216b0f427dc9754824d7f7` and `40d303933d7e3306b6502098b832691f5d6dd864` registered and locked canonical unit/c8 evidence.
- The branch was subsequently reconciled with the repaired #518 parent; predecessor-head evidence is historical and fresh exact-head hosted evidence is required.

## Security and integration boundary

The adapter preserves tenant/project/work-item identifiers from the already-authorized domain event, rejects non-cancellation approval confusion, fails stale optimistic-concurrency writes closed, and requires an opaque generated audit identity. Production bootstrap must keep SQLite foreign keys enabled because enforcement is connection-scoped.

Do not integrate independently of #518/#517/#515. Issue #287 remains open. Subsequent work still needs authenticated server/API wiring, an authoritative same-connection project/work-item version adapter, Rust-first deterministic schedule-variance computation with explicit missingness denominators, and buyer-facing decision views.

## References

SQLite Consortium. (n.d.). *CREATE TABLE*. SQLite. https://www.sqlite.org/lang_createtable.html

SQLite Consortium. (n.d.). *Foreign key support*. SQLite. https://www.sqlite.org/foreignkeys.html

SQLite Consortium. (n.d.). *Savepoints*. SQLite. https://www.sqlite.org/lang_savepoint.html

SQLite Consortium. (n.d.). *SQLite is transactional*. SQLite. https://www.sqlite.org/transactional.html
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
"coverage": "npm run test:coverage",
"server": "node server/server.mjs",
"test:api": "node tests/api/auth-secret.test.mjs && node tests/api/smoke.mjs && node tests/api/ratelimit.test.mjs && node tests/api/attachment-status.test.mjs && node tests/api/session-revocation.test.mjs && node tests/api/orchestrator-attribution.test.mjs",
"test:unit": "node tests/unit/opencode-config.test.mjs && node tests/unit/changelog-release-notes.test.mjs && node tests/unit/analytics.test.mjs && node tests/unit/cpm.test.mjs && node tests/unit/baseline-compare.test.mjs && node tests/unit/workload.test.mjs && node tests/unit/cost-evm.test.mjs && node tests/unit/msproject.test.mjs && node tests/unit/auth-password.test.mjs && node tests/unit/editor-unsaved.test.mjs && node tests/unit/static-coverage-evidence.test.mjs && node tests/unit/dep-types.test.mjs && node tests/unit/weekly-report.test.mjs && node tests/unit/clearfolio.test.mjs && node tests/unit/clearfolio-adapter-mock-hmac.test.mjs && node tests/unit/orchestrator.test.mjs && node tests/unit/orchestrator-coverage.test.mjs && node tests/unit/orchestrator-attribution.test.mjs && node tests/unit/sprint-stats.test.mjs && node tests/unit/burndown.test.mjs && node tests/unit/pm-analysis.test.mjs && node tests/unit/cloud-sync-security.test.mjs && node tests/unit/attachment-status.test.mjs && node tests/unit/clearfolio-status-signal.test.mjs && node tests/unit/work-item-hierarchy.test.mjs && node tests/unit/schedule-outcome-domain.test.mjs && node tests/unit/schedule-outcome-domain-edge.test.mjs && node tests/unit/schedule-reason-event-domain.test.mjs && node tests/unit/schedule-reason-self-approval.test.mjs && node tests/unit/coverage-script-contract.test.mjs && node tests/unit/toast-accessibility.test.mjs",
"test:coverage": "c8 --all --include=app.js --include=cloud-sync.js --include=scripts/ci/static_coverage_evidence.mjs --include=server/attachment_status.mjs --include=server/app.mjs --include=server/auth.mjs --include=server/clearfolio.mjs --include=server/orchestrator.mjs --include=server/work_item_hierarchy.mjs --include=server/schedule_outcome_domain.mjs --include=server/schedule_reason_event_domain.mjs --reporter=json --reporter=json-summary npm run test:coverage:cases",
"test:coverage:cases": "node tests/unit/coverage-script-contract.test.mjs && node tests/unit/attachment-status.test.mjs && node tests/unit/clearfolio-status-signal.test.mjs && node tests/unit/clearfolio-adapter-mock-hmac.test.mjs && node tests/unit/orchestrator.test.mjs && node tests/unit/orchestrator-coverage.test.mjs && node tests/unit/orchestrator-attribution.test.mjs && node tests/unit/work-item-hierarchy.test.mjs && node tests/unit/schedule-outcome-domain.test.mjs && node tests/unit/schedule-outcome-domain-edge.test.mjs && node tests/unit/schedule-reason-event-domain.test.mjs && node tests/unit/schedule-reason-self-approval.test.mjs && node tests/unit/msproject.test.mjs && node tests/unit/auth-password.test.mjs && node tests/unit/editor-unsaved.test.mjs && node tests/unit/static-coverage-evidence.test.mjs && npm run test:api",
"test:unit": "node tests/unit/opencode-config.test.mjs && node tests/unit/changelog-release-notes.test.mjs && node tests/unit/analytics.test.mjs && node tests/unit/cpm.test.mjs && node tests/unit/baseline-compare.test.mjs && node tests/unit/workload.test.mjs && node tests/unit/cost-evm.test.mjs && node tests/unit/msproject.test.mjs && node tests/unit/auth-password.test.mjs && node tests/unit/editor-unsaved.test.mjs && node tests/unit/static-coverage-evidence.test.mjs && node tests/unit/dep-types.test.mjs && node tests/unit/weekly-report.test.mjs && node tests/unit/clearfolio.test.mjs && node tests/unit/clearfolio-adapter-mock-hmac.test.mjs && node tests/unit/orchestrator.test.mjs && node tests/unit/orchestrator-coverage.test.mjs && node tests/unit/orchestrator-attribution.test.mjs && node tests/unit/sprint-stats.test.mjs && node tests/unit/burndown.test.mjs && node tests/unit/pm-analysis.test.mjs && node tests/unit/cloud-sync-security.test.mjs && node tests/unit/attachment-status.test.mjs && node tests/unit/clearfolio-status-signal.test.mjs && node tests/unit/work-item-hierarchy.test.mjs && node tests/unit/schedule-outcome-domain.test.mjs && node tests/unit/schedule-outcome-domain-edge.test.mjs && node tests/unit/schedule-reason-event-domain.test.mjs && node tests/unit/schedule-reason-self-approval.test.mjs && node tests/unit/schedule-reason-event-sqlite.test.mjs && node tests/unit/coverage-script-contract.test.mjs && node tests/unit/toast-accessibility.test.mjs",
"test:coverage": "c8 --all --include=app.js --include=cloud-sync.js --include=scripts/ci/static_coverage_evidence.mjs --include=server/attachment_status.mjs --include=server/app.mjs --include=server/auth.mjs --include=server/clearfolio.mjs --include=server/orchestrator.mjs --include=server/work_item_hierarchy.mjs --include=server/schedule_outcome_domain.mjs --include=server/schedule_reason_event_domain.mjs --include=server/schedule_reason_event_sqlite.mjs --reporter=json --reporter=json-summary npm run test:coverage:cases",
"test:coverage:cases": "node tests/unit/coverage-script-contract.test.mjs && node tests/unit/attachment-status.test.mjs && node tests/unit/clearfolio-status-signal.test.mjs && node tests/unit/clearfolio-adapter-mock-hmac.test.mjs && node tests/unit/orchestrator.test.mjs && node tests/unit/orchestrator-coverage.test.mjs && node tests/unit/orchestrator-attribution.test.mjs && node tests/unit/work-item-hierarchy.test.mjs && node tests/unit/schedule-outcome-domain.test.mjs && node tests/unit/schedule-outcome-domain-edge.test.mjs && node tests/unit/schedule-reason-event-domain.test.mjs && node tests/unit/schedule-reason-self-approval.test.mjs && node tests/unit/schedule-reason-event-sqlite.test.mjs && node tests/unit/msproject.test.mjs && node tests/unit/auth-password.test.mjs && node tests/unit/editor-unsaved.test.mjs && node tests/unit/static-coverage-evidence.test.mjs && npm run test:api",
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed",
"test:e2e:cloud": "playwright install chromium && playwright test tests/e2e/cloud.spec.js tests/e2e/toast-accessibility.spec.js",
Expand Down
Loading