Coming soon · Contract draft
Period Close & Snapshots
A reporting-period contract for readiness, lock/close/reopen lifecycle, immutable snapshots, and asynchronous exports.
Reviewable specification — not callable
Canonical owner
Reporting period, close, snapshot, and export services
Contract posture
P2 · Documentation first · implementation follows approval
Contract metadata
Draft version
0.2 review draft
Review owner
Finance Platform + Developer Platform
Last reviewed
July 14, 2026
Target wave
Wave 5 · Financial operations
Contract dependencies
- • Generic asynchronous Operation resource and status contract
- • Principal × financial scope grantability matrix
- • Transactional outbox and production webhook activation gates
What this unlocks
Assess close readiness
Read a stable domain model for period close & snapshots without depending on GUI routes or database shapes.
Track close tasks
Create a reviewable proposal and apply only the bounded commands the canonical owner permits.
Publish immutable snapshots
React to durable lifecycle facts and connect external systems with explicit direction and provenance.
Lifecycle and invariants
Reporting periods use explicit commands and intermediate operation states. Failed asynchronous work returns the period to its last stable state; it never produces a partially closed period.
openlockedclosingclosedreopening| From | To | Command | Authority |
|---|---|---|---|
| open | locked | Lock period | Finance operator with period-close:lock |
| locked | open | Unlock period | Finance operator before close begins |
| locked | closing | Start close | Named controller with period-close:close |
| closing | closed | Complete close operation | Canonical close worker after all proof obligations pass |
| closed | reopening | Start reopen | Named controller with period-close:reopen and a reason |
| reopening | open | Complete reopen operation | Canonical close worker with immutable audit linkage |
Lifecycle invariants
- • Close cannot begin while blocking readiness exceptions remain unresolved or explicitly waived.
- • The snapshot created by a successful close is immutable and content-addressed.
- • Close and reopen always name an authorized human decision maker, even when a worker performs the operation.
- • A failed close or reopen returns to the prior stable state and preserves the failed operation record.
- • No invoice, time actual, or adjustment may mutate a closed period without an explicit reopen.
Authority boundaries
The interfaces expose canonical capabilities; they do not become a second owner of domain rules or state.
DigitalStack owns
- • Period state and close checklist
- • Snapshot identity and immutability
- • Export operation lifecycle
Explicitly not building
- • GraphQL close mutations
- • Autonomous close or reopen
- • Exposing worker queues instead of operations
Surface decisions
Deliberate additions, retained boundaries, and removals from the proposed external contract.
Readiness, tasks, lock/unlock, close/reopen, snapshots, exports, and Operation resources
The public contract must represent the complete close workflow and its asynchronous results.
REST-only close and reopen with MCP preparation only
High-risk financial commands remain explicit human-authorized operations.
GraphQL close mutations, autonomous MCP close, and raw worker queues
None belongs in a customer-facing financial contract.
Interface plan
REST API
PlannedPeriod reads, readiness, tasks, snapshots, exports, and narrow lock/close/reopen commands.
Read + explicit commands · close and reopen are asynchronous human-authorized operations
GraphQL
PlannedComposable period, readiness, exception, task, snapshot, and operation reads.
Read only · no lock, close, reopen, or export mutations
MCP / Agent API
PlannedReadiness explanation, exception analysis, and preparation of a human close request.
Read + prepare request · the agent cannot lock, close, or reopen a period
Webhooks
PlannedDurable period, snapshot, and export lifecycle facts emitted after canonical commit.
Past-tense facts only · no progress ticks or commands disguised as events
Connected Apps
PlannedERP and analytics consumers receive approved snapshots or exports with explicit direction.
Outbound/read-only · connected systems cannot close or reopen DigitalStack periods
Cross-interface parity
Each surface delegates to the named canonical owner; a blank surface is an intentional denial of authority, not missing documentation.
| Capability | REST | GraphQL | MCP | Webhook | Canonical owner |
|---|---|---|---|---|---|
| Read period | GET /reporting-periods/{id} | reportingPeriod | get_close_readiness | — | Reporting period service |
| Assess readiness | GET /{id}/readiness | ReportingPeriod.readiness | get_close_readiness · explain_close_exception | — | Close-readiness service |
| Lock / unlock period | POST /{id}/lock · POST /{id}/unlock | Not exposed | Not executable | reporting_period.locked · reporting_period.unlocked | Reporting period service |
| Close period | POST /{id}/close → Operation | Not exposed | prepare_close_request only | reporting_period.closed | Close orchestration service |
| Reopen period | POST /{id}/reopen → Operation | Not exposed | Not exposed | reporting_period.reopened | Close orchestration service |
| Export snapshot | POST /{id}/exports → Operation | Operation status read only | get_operation_status | export.completed · export.failed | Snapshot and export services |
Proposed scopes
Proposed scopes are not grantable yet.
period-close:readplanned scopeperiod-close:lockplanned scopeperiod-close:closeplanned scopeperiod-close:reopenplanned scopeperiod-close:exportplanned scopeBehavioral contract
Cross-cutting rules every implementation and interface must satisfy.
Optimistic concurrency
Every command requires If-Match with the current period version; stale commands fail with stale_version.
Command idempotency
Lock, close, reopen, and export require Idempotency-Key and return the original operation on a safe retry.
Asynchronous operations
Close, reopen, and export return 202 with an Operation resource. Clients poll operations or subscribe to terminal events.
Atomic state transition
A period becomes closed only after validation, snapshot creation, audit append, and event enqueue commit atomically.
Named error contract
Clients can branch on period_not_ready, stale_version, period_locked, operation_conflict, and scope_required.
Durable attribution
The deciding human, submitting principal, worker operation, reason, and resulting snapshot remain linked in audit history.
REST API
Proposed endpoints
/api/v1/reporting-periodsList reporting periods
List authorized periods by fiscal year, state, or close date.
period-close:read
- Principal
- Finance viewer or operator
- Execution
- Synchronous
- Named errors
- scope_required
/api/v1/reporting-periods/{id}Retrieve a reporting period
Return lifecycle state, version, readiness summary, latest snapshot, and allowed actions.
period-close:read
- Principal
- Finance viewer or operator
- Execution
- Synchronous
- Named errors
- period_not_found, scope_required
/api/v1/reporting-periods/{id}/readinessGet close readiness
Return blocking exceptions, warnings, task completion, waivers, and readiness version.
period-close:read
- Principal
- Finance viewer or operator
- Execution
- Synchronous
- Named errors
- period_not_found, scope_required
/api/v1/reporting-periods/{id}/tasksList close tasks
Return the close checklist, owners, evidence, due state, and blocking classification.
period-close:read
- Principal
- Finance viewer or operator
- Execution
- Synchronous
- Named errors
- period_not_found, scope_required
/api/v1/reporting-periods/{id}/lockconfirmationLock a period
Lock an open period using the current period and readiness versions.
period-close:lock
- Principal
- Finance operator
- Execution
- Synchronous
- Idempotency
- Required for every command
- Named errors
- period_not_ready, stale_version, operation_conflict
/api/v1/reporting-periods/{id}/unlockconfirmationUnlock a period
Return a locked period to open before a close operation begins.
period-close:lock
- Principal
- Finance operator
- Execution
- Synchronous
- Idempotency
- Required for every command
- Named errors
- period_not_locked, stale_version, operation_conflict
/api/v1/reporting-periods/{id}/closeconfirmationStart period close
Revalidate readiness and enqueue one atomic close operation.
period-close:close
- Principal
- Named controller
- Execution
- Asynchronous operation
- Idempotency
- Required; retries return the original operation
- Named errors
- period_not_ready, stale_version, operation_conflict, scope_required
/api/v1/reporting-periods/{id}/reopenconfirmationStart period reopen
Submit a separately authorized reopen with a durable reason.
period-close:reopen
- Principal
- Named controller
- Execution
- Asynchronous operation
- Idempotency
- Required; retries return the original operation
- Named errors
- period_not_closed, stale_version, operation_conflict, scope_required
/api/v1/reporting-periods/{id}/snapshotsList immutable snapshots
List approved snapshots, checksums, close operations, and export availability.
period-close:read
- Principal
- Finance viewer or operator
- Execution
- Synchronous
- Named errors
- period_not_found, scope_required
/api/v1/reporting-periods/{id}/exportsconfirmationCreate snapshot export
Create an asynchronous export from an immutable approved snapshot.
period-close:export
- Principal
- Finance operator or analytics exporter
- Execution
- Asynchronous operation
- Idempotency
- Required per snapshot, format, and destination
- Named errors
- snapshot_not_found, export_not_allowed, operation_conflict
/api/v1/operations/{id}Get operation status
Retrieve close, reopen, or export progress and terminal result.
period-close:read
- Principal
- Submitting principal or authorized finance viewer
- Execution
- Synchronous
- Named errors
- operation_not_found, scope_required
Start an authorized period close
curl --request POST \
+ --url https://www.digitalstack360.com/api/v1/reporting-periods/period_2026_06/close \
+ --header "Authorization: Bearer $DSTACK_API_KEY" \
+ --header "Content-Type: application/json" \
+ --header 'If-Match: "7"' \
+ --header "Idempotency-Key: close-period-2026-06-v7" \
+ --data '{
"readiness_version": 12,
"decision_maker_id": "user_controller",
"reason": "June close checklist reviewed and approved"
}'{
"data": {
"id": "op_close_01k5...",
"type": "reporting_period.close",
"status": "queued",
"target": {
"type": "reporting_period",
"id": "period_2026_06",
"expected_version": 7
},
"submitted_by": "api_key_123",
"decision_maker_id": "user_controller",
"submitted_at": "2026-07-16T14:22:04Z"
}
}GraphQL
Proposed graph
Types
ReportingPeriodCloseReadinessCloseExceptionCloseTaskPeriodSnapshotOperationQueries
reportingPeriod(id: ID!): ReportingPeriodreportingPeriods(filter: ReportingPeriodFilter, pagination: PaginationInput): ReportingPeriodConnection!operation(id: ID!): OperationNo GraphQL mutations planned
query PeriodCloseReadiness($id: ID!) {
reportingPeriod(id: $id) {
id
label
state
version
readiness {
version
ready
blockingCount
warningCount
tasks { id title state blocking owner { id displayName } }
exceptions { id code severity message canWaive }
}
latestSnapshot { id createdAt checksum }
allowedActions { id label requiresConfirmation requiredScope }
}
}MCP / Agent API
Proposed tools
Get close readiness
get_close_readinessRead the current period, checklist, exceptions, waivers, and allowed human actions.
- Scope
- period-close:read
- Input
- Reporting period id.
- Output
- Bounded readiness summary with evidence references and version.
- Write boundary
- Read only.
Explain a close exception
explain_close_exceptionExplain one blocking or warning exception using governed evidence.
- Scope
- period-close:read
- Input
- Reporting period id and exception id.
- Output
- Explanation, affected records, remediation options, and source references.
- Write boundary
- Read only; never waives or resolves the exception.
Prepare a close request
prepare_close_requestValidate readiness and prepare the exact REST command for a named controller.
- Scope
- period-close:close
- Input
- Period id, expected version, readiness version, decision maker, and reason.
- Output
- A non-executing request package with validation results and command preview.
- Write boundary
- The agent cannot submit, approve, or execute close.
Get operation status
get_operation_statusRead a close, reopen, or export operation and its terminal result.
- Scope
- period-close:read
- Input
- Operation id.
- Output
- Operation state, timestamps, error code, and result references.
- Write boundary
- Read only; cancellation is not exposed until the domain defines a safe window.
User: Are we ready to close June, and what would closing it do?
1. Call get_close_readiness for period_2026_06.
2. Call explain_close_exception for each blocking exception the user asks about.
3. If readiness passes, call prepare_close_request with the named controller and current versions.
4. Show the immutable snapshot, locked-data, and downstream export consequences.
5. Hand the prepared request to the authorized human workflow.
Never call the close or reopen REST command from MCP.Webhooks
Proposed event catalog
Event types
reporting_period.openedplanned eventA reporting period became open for operational activity.
payload: reporting_period_id, period_start, period_end, opened_at
reporting_period.lockedplanned eventAn authorized operator locked the period.
payload: reporting_period_id, version, locked_by, locked_at
reporting_period.unlockedplanned eventAn authorized operator returned the period to open before close began.
payload: reporting_period_id, version, unlocked_by, unlocked_at
reporting_period.closedplanned eventThe atomic close completed and produced an immutable snapshot.
payload: reporting_period_id, snapshot_id, close_operation_id, version, closed_at
reporting_period.reopenedplanned eventAn authorized reopen completed with a durable reason.
payload: reporting_period_id, reopen_operation_id, reason_code, version, reopened_at
snapshot.completedplanned eventSnapshot materialization completed successfully.
payload: snapshot_id, reporting_period_id, checksum, completed_at
snapshot.failedplanned eventSnapshot materialization failed without closing the period.
payload: reporting_period_id, operation_id, error_code, failed_at
export.completedplanned eventA requested snapshot export became available.
payload: export_id, snapshot_id, format, operation_id, completed_at
export.failedplanned eventA requested export failed.
payload: export_id, snapshot_id, operation_id, error_code, failed_at
{
"event_id": "evt_01k5...",
"type": "reporting_period.closed",
"event_version": 1,
"occurred_at": "2026-07-16T14:24:31Z",
"workspace_id": "ws_abc123",
"resource": {
"type": "reporting_period",
"id": "period_2026_06"
},
"actor": {
"id": "user_controller"
},
"payload": {
"reporting_period_id": "period_2026_06",
"snapshot_id": "snapshot_01k5...",
"close_operation_id": "op_close_01k5...",
"version": 8,
"closed_at": "2026-07-16T14:24:31Z"
},
"source": null
}Connected Apps
Proposed connection roles
Claude / ChatGPT / IDE clients
Consumer application
Explain readiness and prepare a non-executing close request.
Authority: Read and prepare only; cannot lock, close, reopen, waive exceptions, or approve.
ERP / accounting systems
Synchronization provider
Receive approved snapshot references or reconcile exported financial facts.
Authority: Outbound after close by default; provider state cannot close or reopen DigitalStack periods.
Data warehouse / BI
Analytics sink
Consume immutable period snapshots and asynchronous exports.
Authority: Read/export only; cannot mutate financial operations or operational records.
Open contract decisions
Resolve before implementation approval
- • Must reopen require two distinct human approvals, or one controller plus an audited reason?
- • What cancellation window, if any, is safe while a close operation is still queued or validating?
- • What retention and redelivery guarantees apply to large snapshot exports?
- • Which readiness warnings can be waived, and which roles may create those waivers?
- • Must reopen require dual approval for enterprise accounts?
Proof obligations
| Must remain true | Failure indicator |
|---|---|
| The reporting-period service remains the singular period-state owner. | A worker, GraphQL resolver, MCP tool, or ERP persists an alternate close state. |
| Close is atomic across validation, snapshot, audit, and event enqueue. | A period is closed without a snapshot, audit decision, or durable outbox event. |
| Closed-period snapshots are immutable. | An export or reconciliation flow changes snapshot content or checksum after close. |
| Close and reopen are attributable human decisions. | An agent, schedule, worker, or connector becomes the recorded decision maker. |
| Failed operations preserve the prior stable state. | A failed close leaves the period partially closed or financially inconsistent. |
| Every command is versioned and idempotent. | A stale request overwrites newer state or a retry creates duplicate operations. |
| Financial data remains separately scoped and minimized. | A period read or event leaks invoice, rate, margin, or personal data beyond its declared scope. |