-
Notifications
You must be signed in to change notification settings - Fork 189
feat(api): add accounting periods to spec #4365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tothandras
wants to merge
2
commits into
main
Choose a base branch
from
feat/api-v3-closing
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| import "./period.tsp"; | ||
| import "./operations.tsp"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| import "@typespec/http"; | ||
| import "@typespec/rest"; | ||
| import "@typespec/openapi"; | ||
| import "@typespec/openapi3"; | ||
| import "../common/error.tsp"; | ||
| import "../common/pagination.tsp"; | ||
| import "../common/parameters.tsp"; | ||
| import "../common/properties.tsp"; | ||
| import "../shared/index.tsp"; | ||
| import "./period.tsp"; | ||
|
|
||
| using TypeSpec.Http; | ||
| using TypeSpec.OpenAPI; | ||
|
|
||
| namespace Accounting; | ||
|
|
||
| /** | ||
| * Filter options for listing accounting periods. | ||
| */ | ||
| @friendlyName("ListAccountingPeriodsParamsFilter") | ||
| model ListAccountingPeriodsParamsFilter { | ||
| /** | ||
| * Filter periods by lifecycle status. Use `filter[status][oeq]=open,closing` to | ||
| * fetch the currently-active periods. | ||
| */ | ||
| #suppress "@openmeter/api-spec-aip/doc-decorator" "filter field" | ||
| status?: Common.StringFieldFilterExact; | ||
|
|
||
| /** | ||
| * Filter periods by `start_at`. | ||
| */ | ||
| #suppress "@openmeter/api-spec-aip/doc-decorator" "filter field" | ||
| start_at?: Common.DateTimeFieldFilter; | ||
|
|
||
| /** | ||
| * Filter periods by `end_at`. Open periods (no `end_at`) are excluded when this | ||
| * filter is applied. | ||
| */ | ||
| #suppress "@openmeter/api-spec-aip/doc-decorator" "filter field" | ||
| end_at?: Common.DateTimeFieldFilter; | ||
|
|
||
| /** | ||
| * Filter periods by labels. | ||
| */ | ||
| #suppress "@openmeter/api-spec-aip/doc-decorator" "filter field" | ||
| labels?: Common.LabelsFieldFilter; | ||
| } | ||
|
|
||
| /** | ||
| * Request body for initiating a close on the currently-open accounting period. | ||
| */ | ||
| @friendlyName("CloseAccountingPeriodRequest") | ||
| @summary("Close accounting period request") | ||
| model CloseAccountingPeriodRequest { | ||
| /** | ||
| * The intended end of the period being closed (exclusive). Must be strictly | ||
| * greater than the open period's `start_at` and not in the future. | ||
| * | ||
| * Becomes the closing period's `end_at` and the newly-opened next period's | ||
| * `start_at`. | ||
| */ | ||
| @summary("End of period") | ||
| end_at: Shared.DateTime; | ||
|
|
||
| /** | ||
| * Optional labels applied to the closing period. | ||
| */ | ||
| @summary("Labels") | ||
| labels?: Common.Labels; | ||
|
|
||
| /** | ||
| * Optional display name for the period (e.g. "2026-Q2"). Auto-generated if | ||
| * omitted. | ||
| */ | ||
| @summary("Name") | ||
| @minLength(1) | ||
| @maxLength(256) | ||
| name?: string; | ||
|
|
||
| /** | ||
| * Optional description. | ||
| */ | ||
| @summary("Description") | ||
| @maxLength(1024) | ||
| description?: string; | ||
| } | ||
|
|
||
| interface AccountingPeriodsOperations { | ||
| /** | ||
| * Close the currently-open accounting period. | ||
| * | ||
| * Atomically, under a namespace-period advisory lock: | ||
| * | ||
| * 1. Freezes membership on the currently-open period `P`: sets `P.end_at`, | ||
| * transitions `P.status` to `closing`, and records `P.closing_started_at`. | ||
| * 2. Opens a new period `N` with `N.start_at = P.end_at` and `N.status = open`. | ||
| * | ||
| * The transition `closing → closed` (balance snapshot computation, close-time | ||
| * validations, and seal hash commit) is performed by the close pipeline; poll | ||
| * `GET .../{periodId}` to observe the final `closed` state, `closed_at`, | ||
| * `sealed_at`, and seal fields. | ||
| * | ||
| * Late-arriving transactions whose `booked_at` falls inside the now-closed period | ||
| * are posted to `N` with an `adjusts_period_id` backpointer to `P`; they do not | ||
| * modify `P`. | ||
| * | ||
| * Returns the period transitioned to `closing`. The newly-opened next period can | ||
| * be retrieved by listing with `filter[status][eq]=open`. | ||
| */ | ||
| @extension(Shared.UnstableExtension, true) | ||
| @extension(Shared.InternalExtension, true) | ||
| @post | ||
| @route("/close") | ||
| @operationId("close-accounting-period") | ||
| @summary("Close the open accounting period") | ||
| close( | ||
| @body body: CloseAccountingPeriodRequest, | ||
| ): Shared.UpdateResponse<AccountingPeriod> | Common.ErrorResponses; | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| /** | ||
| * Get an accounting period by id. | ||
| */ | ||
| @extension(Shared.UnstableExtension, true) | ||
| @extension(Shared.InternalExtension, true) | ||
| @get | ||
| @operationId("get-accounting-period") | ||
| @summary("Get an accounting period") | ||
| get( | ||
| @path periodId: Shared.ULID, | ||
| ): Shared.GetResponse<AccountingPeriod> | Common.NotFound | Common.ErrorResponses; | ||
|
|
||
| /** | ||
| * List accounting periods. | ||
| * | ||
| * Returns the namespace's periods in reverse-chronological order by `start_at` by | ||
| * default (most recent first). | ||
| */ | ||
| @extension(Shared.UnstableExtension, true) | ||
| @extension(Shared.InternalExtension, true) | ||
| @get | ||
| @operationId("list-accounting-periods") | ||
| @summary("List accounting periods") | ||
| list( | ||
| ...Common.PagePaginationQuery, | ||
|
|
||
| /** | ||
| * Sort periods returned in the response. Supported sort attributes are: | ||
| * | ||
| * - `start_at` (default, descending) | ||
| * - `created_at` | ||
| * - `id` | ||
| */ | ||
| @query(#{ name: "sort" }) | ||
| sort?: Common.SortQuery, | ||
|
|
||
| /** | ||
| * Filter periods returned in the response. | ||
| */ | ||
| @query(#{ style: "deepObject", explode: true }) | ||
| filter?: ListAccountingPeriodsParamsFilter, | ||
| ): Shared.PagePaginatedResponse<AccountingPeriod> | Common.ErrorResponses; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| import "../shared/index.tsp"; | ||
|
|
||
| namespace Accounting; | ||
|
|
||
| /** | ||
| * Lifecycle status of an accounting period. | ||
| * | ||
| * - `open`: the current posting bucket. New transactions can be posted to this | ||
| * period. Exactly one period per namespace is `open` at any time. | ||
| * - `closing`: membership is frozen — no new transactions can post to this period | ||
| * — but balance snapshots and the seal have not yet been committed. At most one | ||
| * period per namespace is `closing` at any time. | ||
| * - `closed`: the period is sealed. `closed_at`, `sealed_at`, and (when sealing is | ||
| * enabled) `seal_hash` are set. Transaction membership and the closing balance | ||
| * snapshots are immutable. | ||
| */ | ||
| @friendlyName("AccountingPeriodStatus") | ||
| @summary("Accounting period status") | ||
| enum AccountingPeriodStatus { | ||
| // The current posting bucket for new transactions. | ||
| Open: "open", | ||
|
|
||
| // Membership is frozen but the seal has not yet been committed. | ||
| Closing: "closing", | ||
|
|
||
| // The period is sealed and immutable. | ||
| Closed: "closed", | ||
| } | ||
|
|
||
| /** | ||
| * An accounting period is a sequential, non-overlapping window of ledger activity | ||
| * within a namespace. Closing a period freezes its transaction membership and | ||
| * (eventually) seals an authoritative closing balance. | ||
| * | ||
| * Late-arriving transactions whose `booked_at` falls inside an already-closed | ||
| * period are posted to the current open period with an `adjusts_period_id` | ||
| * backpointer; they do not mutate the closed period. | ||
| */ | ||
| @friendlyName("AccountingPeriod") | ||
| @summary("Accounting period") | ||
| model AccountingPeriod { | ||
| ...Shared.ResourceImmutable; | ||
|
|
||
| /** | ||
| * Start of the period (inclusive). | ||
| * | ||
| * For the first period in a namespace this is the namespace creation time. For | ||
| * every subsequent period, equals the previous period's `end_at`. Immutable once | ||
| * set. | ||
| */ | ||
| @visibility(Lifecycle.Read) | ||
| @summary("Start of period") | ||
| start_at: Shared.DateTime; | ||
|
|
||
| /** | ||
| * End of the period (exclusive). | ||
| * | ||
| * Unset while the period is `open`. Fixed at the moment a close is initiated, and | ||
| * immutable thereafter. | ||
| */ | ||
| @visibility(Lifecycle.Read) | ||
| @summary("End of period") | ||
| end_at?: Shared.DateTime; | ||
|
|
||
| /** | ||
| * Current lifecycle status of the period. | ||
| */ | ||
| @visibility(Lifecycle.Read) | ||
| @summary("Status") | ||
| status: AccountingPeriodStatus; | ||
|
|
||
| /** | ||
| * Timestamp when the close was initiated (open → closing transition). Set on | ||
| * `closing` and `closed` periods. | ||
| */ | ||
| @visibility(Lifecycle.Read) | ||
| @summary("Closing started at") | ||
| closing_started_at?: Shared.DateTime; | ||
|
|
||
| /** | ||
| * Timestamp when the period was marked `closed` (closing → closed transition). Set | ||
| * only on `closed` periods. | ||
| */ | ||
| @visibility(Lifecycle.Read) | ||
| @summary("Closed at") | ||
| closed_at?: Shared.DateTime; | ||
|
|
||
| /** | ||
| * Timestamp when the seal hash was committed. Set only on `closed` periods that | ||
| * have a seal hash. | ||
| */ | ||
| @visibility(Lifecycle.Read) | ||
| @summary("Sealed at") | ||
| sealed_at?: Shared.DateTime; | ||
|
|
||
| /** | ||
| * Cryptographic hash sealing the period. The hash covers the period's frozen | ||
| * transaction membership and closing balance snapshots in a deterministic, | ||
| * versioned input format described by `seal_algorithm` and `seal_input_version`. | ||
| * | ||
| * Set only on `closed` periods that have been sealed. | ||
| */ | ||
| @visibility(Lifecycle.Read) | ||
| @summary("Seal hash") | ||
| seal_hash?: string; | ||
|
|
||
| /** | ||
| * Algorithm used to compute `seal_hash`. Example: `"sha256"`. | ||
| */ | ||
| @visibility(Lifecycle.Read) | ||
| @summary("Seal algorithm") | ||
| seal_algorithm?: string; | ||
|
|
||
| /** | ||
| * Version of the seal input format. Versioning the input format lets verifiers | ||
| * reproduce a historical seal even after the format evolves. | ||
| */ | ||
| @visibility(Lifecycle.Read) | ||
| @summary("Seal input version") | ||
| seal_input_version?: string; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.