feat(numbering): a number series is a tenant-level business object - the .numbers artefact declares it - #6476
Conversation
DSL: NumberIntent keeps series + optional per (a to-one that partitions the series) + stampOn. format, scope and resetOn are gone - a number's shape is a deployment concern, not a model one. Runtime reshaped to prefix + sequence padded to size, no token grammar; a series must be declared before it can be allocated from (allocating an unknown series fails loudly rather than inventing a shape nobody chose). Store rows are one per (series, partition), each carrying the shape AND the counter, with every writer touching only its own columns. Not yet functional: the .numbers artefact + synchronizer that provisions series per tenant is the next commit. Until it lands, nothing can provision a series, so allocation fails by design. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ings edit shape + next - NumberSeriesDeclaration artefact + repository + service + multitenant NumberSeriesSynchronizer (SynchronizersOrder.NUMBER_SERIES = 45, before anything allocating): absent series -> provision per tenant; identical cross-module re-declaration -> skip; differing -> fail that artefact loudly naming both modules; DELETE/cleanup never touch a series row. A FAILED declaration is re-evaluated on UPDATE and stays depleted, so the processor never overwrites the conflict message. - .numbers content type (application/json+numbers) in ContentTypeHelper. - Partition rows materialize on first allocation from the series' base row (partition values are data - no artefact can pre-provision them); undeclared series still fail loudly. - IntentParser rejects the removed number keys format/scope/resetOn on the raw YAML tree - the typed Gson mapping silently dropped them. - Application shell Document Numbering settings: per-series prefix + total width + next value with a live example, change-tracked writes (PUT /shape for shape, PUT for next - unchanged values never written). - NumberingSdkIT rewritten: declared-series allocation, undeclared 500, differing re-declaration FAILED with both locations, per-partition independence, and a new-tenant own-sequence case. - Docs: intent-assistant-guide.md number: section, engine-intent CLAUDE.md numbering bullet, root CLAUDE.md summary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| /** The current tenant's series. */ | ||
| @GetMapping | ||
| public ResponseEntity<List<DocumentNumberStore.Counter>> list() { | ||
| public ResponseEntity<List<SeriesView>> list() { |
|
|
||
| /** The numeric sequence value of a rendered {@code T-NNNN} number. */ | ||
| private static int value(String number) { | ||
| return Integer.parseInt(number.substring(2)); |
…synchronizer switch CodeQL on #6476: list() no longer bootstraps the counter table (reads return an empty list before any writer created it, and read pre-upgrade shape columns as defaults); the artefact-phase switch names PREPARE explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…generate, publish and REST IntentEmissionCoverageIT gains the numbering keyword at both layers: the generated repository allocates from the DECLARED series by name (DocumentNumbers.next), and the published app stamps prefix + zero-padded, gap-free numbers at insert - provisioned from an AUTHORED .numbers file published with the project (which the intent scrub correctly leaves alone). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| assertEquals(Integer.parseInt(firstNumber.get() | ||
| .substring(3)) |
| Integer.parseInt(secondNumber.get() | ||
| .substring(3)), |
|
On the CodeQL check: the one high-severity finding ( Commit 216b26f still tightened the actual substance behind it: the GET path no longer performs ANY database write (the create-if-absent table bootstrap moved off the read path entirely - reads return an empty list before any writer created the table). The query keeps flagging the endpoint pattern regardless, same as on master, so it is a candidate for dismissal alongside the master alerts. The remaining three alerts are severity-note 🤖 Generated with Claude Code |
What
First-class document numbering, reworked so the number's shape leaves the model. An intent field declares only
number: { series, per?, stampOn }- a reference to a series by name. The series itself is a tenant-level business object: a number renders as a literal prefix + the sequence zero-padded to a total width (no token grammar), and its shape and counter live per tenant, not in any module.The
.numbersartefact (new)A module DECLARES A REQUIREMENT - "I need series X; if this tenant has none, provision it with this prefix/width" - exactly as
.rolesdeclares roles:{"series": [{"name": "Sales Invoice", "prefix": "SI", "size": 10}]}NumberSeriesSynchronizer(engine-numbering, multitenant,SynchronizersOrder.NUMBER_SERIES = 45- before anything that could allocate):A FAILED declaration is re-evaluated on UPDATE (the other module may have re-aligned) and stays depleted, so the processor never buries the conflict message under "undepleted artefact" noise.
One table, one writer per column
DIRIGIBLE_DOCUMENT_NUMBERS, one row per (series, partition), holds shape AND counter. The synchronizer only INSERTs missing rows; the Settings endpoint writes PREFIX/SIZE and the counter reset; the allocator increments COUNTER. One deliberate exception: the first allocation for a new partition (per: Company- partition values are data, no artefact can pre-provision them) materializes that partition's row from the series' base row, inheriting the tenant's configured shape. Allocating an undeclared series fails loudly - a shape is never invented.Sequences are continuous and never auto-reset - a jurisdiction that restarts numbering each January does it by setting prefix + next in the settings (BG law requires continuous, so that is the default posture).
Removed keys fail loudly
An intent still carrying
number.format/scope/resetOnused to be silently dropped by the typed Gson mapping.IntentParser.rejectRemovedNumberKeysnow rejects them on the raw YAML tree with messages pointing at.numbers/per:/ the settings.Settings UI
The application shell's Document Numbering page now edits each series' prefix, total width and next value, with a live example of the next number. Writes are change-tracked (
PUT /services/core/numbering/shapefor shape,PUTfor next) - an unchanged "next" is never written, so a counter that advanced since page load is never rewound.Tests
engine-numbering17/17 unit tests (synchronizer parse/validation, provision, cross-module conflict, FAILED re-evaluation, delete-never-touches-counters; rendering).engine-intent285/285 (incl. the new removed-keys rejections; one staleformat:fixture the new check caught was migrated).NumberingSdkIT5/5 green locally (H2): gap-free allocation across re-syncs, undeclared-series 500, differing re-declaration FAILED naming both modules with the original shape surviving, per-partition independence, and a freshly provisioned tenant drawingT-0001from its own sequence.formatter:validategreen.Docs:
intent-assistant-guide.md,engine-intent/CLAUDE.md, rootCLAUDE.md(this PR); dirigible-io/dirigible-io.github.io#157 (merged); IntentFile/intentfile.github.io#2 (open).🤖 Generated with Claude Code