feat: preserve nested source projects across desktop and REST

This commit is contained in:
2026-09-06 00:17:20 +03:00
parent d364d3c058
commit 4e8575bd11
32 changed files with 4283 additions and 417 deletions
+84 -14
View File
@@ -631,14 +631,17 @@ desktop.</p>
<h2>Identity, revision and writes</h2>
<p>All model routes begin with <span class="docutils literal">/api/v1</span>. A hosted project's immutable service
<span class="docutils literal">resource_id</span> is different from its editable <span class="docutils literal">model.id</span>. Each stored file
contains <span class="docutils literal">{resource_id, revision, model}</span>. The revision starts at 1, increments
once per changed accepted transaction, and does not change for a no-op.</p>
contains <span class="docutils literal">{resource_id, revision, model, source_project}</span>. <span class="docutils literal">source_project</span>
is the accepted entry filename and map of authored source files; compiling it
must reproduce <span class="docutils literal">model</span>. The revision starts at 1, increments once per changed
accepted model/source pair, and does not change for an exact no-op. Comment-only
edits and file/entry changes count as changes even when the model is unchanged.</p>
<p>Existing-project writes require the exact quoted ETag returned by a project
read, for example:</p>
<pre class="literal-block">If-Match: &quot;p-6f7162cc-094c-4f30-9b70-3f6f355f0c7f-r1&quot;</pre>
<p>Missing preconditions return 428; stale preconditions return 412. Weak ETags,
wildcards and lists are not accepted on writes. GET of the envelope, model and
source supports <span class="docutils literal"><span class="pre">If-None-Match</span></span> and returns an empty 304 when unchanged.
source/source-project supports <span class="docutils literal"><span class="pre">If-None-Match</span></span> and returns an empty 304 when unchanged.
Use ETags as opaque strings; revisions are unsigned integer counters in JSON.</p>
<p>Every accepted changed write is persisted before the response. A worker captures
a revision-qualified snapshot, then parses and validates its candidate outside
@@ -649,7 +652,8 @@ Malformed or rejected inputs preserve the accepted model. Incomplete-model
warnings permit work and are returned in <span class="docutils literal">issues</span>.</p>
<p>There is no retained revision history, idempotency receipt, tombstone or
server undo. DELETE permanently removes the hosted envelope. Source/JSON
exports contain the current model only. They preserve the model's persistent
interchange exports contain the current model only; source-project export also
preserves the authored file layout and comments. They preserve persistent
aliases, diagrams, artifact references and OA/SA trace data. Artifact revision
strings refer to external artifacts, not past server snapshots.</p>
<p>A lost connection or 504 can occur after an admitted write has begun. That
@@ -707,6 +711,10 @@ bearer token, if any. Non-loopback binding requires a configured token.</p>
<td><p><span class="docutils literal">/api/v1/projects/import</span></p></td>
<td><p>Raw model JSON or Arcadia source; new service ID.</p></td>
</tr>
<tr><td><p>POST</p></td>
<td><p><span class="docutils literal"><span class="pre">/api/v1/projects/import-source-project</span></span></p></td>
<td><p>Complete source bundle as JSON; stabilize missing IDs and create a resource.</p></td>
</tr>
<tr><td><p>GET / PUT</p></td>
<td><p><span class="docutils literal"><span class="pre">/api/v1/projects/{id}/model</span></span></p></td>
<td><p>Export or conditionally replace native model JSON.</p></td>
@@ -715,6 +723,10 @@ bearer token, if any. Non-loopback binding requires a configured token.</p>
<td><p><span class="docutils literal"><span class="pre">/api/v1/projects/{id}/source</span></span></p></td>
<td><p>Export or conditionally replace canonical UTF-8 source.</p></td>
</tr>
<tr><td><p>GET / PUT</p></td>
<td><p><span class="docutils literal"><span class="pre">/api/v1/projects/{id}/source-project</span></span></p></td>
<td><p>Export exact authored files or conditionally replace the complete bundle.</p></td>
</tr>
<tr><td><p>GET</p></td>
<td><p><span class="docutils literal"><span class="pre">/api/v1/projects/{id}/elements</span></span></p></td>
<td><p>Filter by <span class="docutils literal">kind</span>, <span class="docutils literal">layer</span>, <span class="docutils literal">alias</span>, name <span class="docutils literal">q</span>.</p></td>
@@ -757,7 +769,10 @@ bearer token, if any. Non-loopback binding requires a configured token.</p>
<span class="docutils literal">text/plain; <span class="pre">charset=utf-8</span></span> is used for Arcadia source. The service does not
guess formats from a filename. Unknown DTO fields, invalid UUIDs/enums, duplicate
model map keys and unsupported schemas are rejected. Maximum body size is
8 MiB. Compressed or encoded request bodies and browser Origin requests are
8 MiB. Accepted serialized model JSON and serialized source-bundle JSON each
have an 8 MiB cap, including IDs inserted during compilation. The stored
envelope allows both bounded representations plus small framing overhead.
Compressed or encoded request bodies and browser Origin requests are
not supported.</p>
<p>Model admission permits at most 5,000 elements plus relations, 20,000 total
persistent records (including the project, views and placements), and 50,000
@@ -770,10 +785,54 @@ Parsing, validation and query processing use snapshot workers, so this work
does not hold the catalog lock or serialize unrelated model preparation.</p>
<p>Import accepts schema-1 JSON through the shared explicit migration adapter,
as well as schema-2 native JSON. Source import uses the shared language parser
and its supported migrations. Export and native JSON replacement use schema 2.
and its supported migrations. Export uses schema 3 / arcadia 3. Native JSON
replacement accepts supported older schemas through the same explicit migration.
Only documented query parameters are accepted. In particular, appending
<span class="docutils literal"><span class="pre">?revision=...</span></span> to model/source export is an error, not historical retrieval.</p>
</section>
<section id="authored-source-projects">
<h2>Authored source projects</h2>
<p>A source bundle contains <span class="docutils literal">entry</span> and a map of <span class="docutils literal">files</span>. For example:</p>
<pre class="literal-block">{
&quot;entry&quot;: &quot;rescue.arcadia&quot;,
&quot;files&quot;: {
&quot;rescue.arcadia&quot;: &quot;arcadia 3; project rescue \&quot;Rescue\&quot; { include \&quot;oa/work.arcadia\&quot;; }\n&quot;,
&quot;oa/work.arcadia&quot;: &quot;// Engineering rationale\noa_activity assess \&quot;Assess assistance\&quot; {}\n&quot;
}
}</pre>
<p>The entry is a root filename. All keys are canonical, portable, slash-separated
relative paths ending in <span class="docutils literal">.arcadia</span>. Includes resolve only against these
submitted strings. They never read the service filesystem, URLs, artifact
targets or another hosted resource. All submitted files must be reachable;
missing files, cycles, duplicate keys, case collisions and escaping paths are
rejected. The bundle permits at most 256 files, 32 include edges along a path,
and 1,024 include edges in total. Model and source-byte budgets apply across
the complete bundle, not separately per file. Bundle syntax is source3;
single-file legacy migration remains available through interchange import.</p>
<p>Compilation inserts UUIDs into headers that omit them. Import returns 201 with
Location/ETag; GET source-project retrieves the stabilized accepted bundle.
PUT returns <span class="docutils literal">revision</span>, <span class="docutils literal">changed</span>, located <span class="docutils literal">issues</span> and that accepted
<span class="docutils literal">source_project</span>. Preserve the returned IDs before subsequent editing.
With explicit IDs, untouched text, comments, line endings, entry name and module
ownership are preserved exactly. Re-submitting the same accepted bundle is a
no-op. Removing a file means submitting a complete valid bundle without that
file and its includes; no files are created or deleted on the service host.</p>
<p>All typed command, transition, rename and model-interchange writes update this
same accepted source project. Existing declarations and reference comments
follow command identity correspondence, including UUID swaps. Full-model
replacement uses common UUIDs as its correspondence boundary. It preserves
the accepted source layout; canonical interchange formatting/comments are not
adopted as a replacement authored workspace. Use source-project PUT for
authoring file layout and comments. A failed source writeback rejects the whole
write and never falls back to discarding authored files.</p>
<p>Old model-only envelopes are migrated to a deterministic source bundle in
memory when opened. Reading or an exact no-op does not rewrite their files or
advance revisions. Their next changed write persists both representations.
Startup rejects already-coupled envelopes whose source/model disagree or whose
source lacks stable IDs. The 256 MiB store budget charges complete accepted
envelopes, including the generated source of old envelopes before their next
save; an over-capacity old store is reported rather than silently pruned.</p>
</section>
<section id="queries-and-pagination">
<h2>Queries and pagination</h2>
<p>List filters combine using AND. Enum spelling is case-sensitive and comes
@@ -841,11 +900,14 @@ creation identities allow later commands in the batch to reference earlier
creations. The shared <span class="docutils literal">execute_batch</span> applies all operations to one candidate
and validates the final state. A rejected operation or final structural error
rejects the whole request. Reference rewrites/deletion cleanup follow the same
core logic as desktop editing.</p>
core logic as desktop editing. Command preparation retains original-to-candidate
identity correspondence through UUID edits, swaps, deletion and reuse. Source
writeback uses that correspondence to preserve declaration ownership and comments.</p>
<p>Dry run still requires <span class="docutils literal"><span class="pre">If-Match</span></span>. Its response includes <span class="docutils literal">revision</span>,
<span class="docutils literal">dry_run</span>, <span class="docutils literal">changed</span>, <span class="docutils literal">issues</span> and the proposed <span class="docutils literal">model</span>. It neither writes
nor reserves IDs. A committed response has the same fields except the model is
omitted. Commit must check the revision again. A no-op is successful with
<span class="docutils literal">dry_run</span>, <span class="docutils literal">changed</span>, <span class="docutils literal">issues</span> and the proposed <span class="docutils literal">model</span> and
<span class="docutils literal">source_project</span>. It validates source writeback as well as semantics and neither writes
nor reserves IDs. A committed response omits the proposed model and source
project. Commit must check the revision again. A no-op is successful with
<span class="docutils literal">changed: false</span> and the original ETag.</p>
</section>
<section id="contextual-process-and-chain-topology">
@@ -898,10 +960,15 @@ reinterpret old mixed lifelines as a strict new viewpoint.</p>
<pre class="literal-block">{&quot;format&quot;:&quot;json&quot;, &quot;model&quot;: {&quot;...&quot;:&quot;complete native Project record&quot;}}</pre>
<p>or:</p>
<pre class="literal-block">{&quot;format&quot;:&quot;arcadia&quot;, &quot;source&quot;:&quot;complete source document&quot;}</pre>
<p>or:</p>
<pre class="literal-block">{&quot;format&quot;:&quot;source-project&quot;, &quot;source_project&quot;:{&quot;entry&quot;:&quot;rescue.arcadia&quot;, &quot;files&quot;:{&quot;rescue.arcadia&quot;:&quot;arcadia 3; project rescue \&quot;Rescue\&quot; {}&quot;}}}</pre>
<p>A well-formed validation request returns 200 with <span class="docutils literal">valid</span> and <span class="docutils literal">issues</span> even
if its candidate is invalid. A malformed request envelope or JSON model record
is a 400. Source diagnostics include one-based line and column according to the
shared language parser. Candidate validation does not replace current state.</p>
shared language parser. Candidate validation does not replace current state.
Source-project validation locations refer to the submitted bytes before UUID
insertion. Successful source-project PUT diagnostics refer to the returned
accepted bundle. Missing IDs are not saved or adopted by validation.</p>
</section>
<section id="typed-state-machines-and-events">
<h2>Typed state machines and events</h2>
@@ -934,7 +1001,8 @@ commit and disk persistence contract as other semantic records.</p>
conflict resolutions and optional destination diagram. All fields are explicit
in OpenAPI. At most 100 sources, 200 mappings and 400 field resolutions are
accepted. A conflict-free plan must fit the same 200-command transaction limit
and model-capacity limits.</p>
and model/source-capacity limits. A conflict-free preview also checks source
writeback, so its commands can update the same accepted authored workspace.</p>
<p>The preview response is <span class="docutils literal"><span class="pre">{&quot;base_revision&quot;:1,&quot;plan&quot;:{...}}</span></span>. It contains
planned changes, conflicts, warnings and exact commands/IDs. It does not write
the model or persist a server-side plan. Apply accepts that entire response and
@@ -959,7 +1027,9 @@ not a security digest. There is no plan token, expiration store or receipt.</p>
&quot;current_revision&quot;:3
}</pre>
<p><span class="docutils literal">issues</span> contain severity, code and message, and may include field,
object_id, line and column. Use
object_id, line, column, source <span class="docutils literal">path</span>, half-open UTF-8 byte <span class="docutils literal">span</span>
(<span class="docutils literal">start</span>, <span class="docutils literal">end</span>), and <span class="docutils literal">related</span> source locations (path/span). Lines and
columns are one-based; columns count Unicode scalars. Use
<span class="docutils literal">code</span> for client decisions, not the English message. <span class="docutils literal"><span class="pre">X-Request-ID</span></span> is a
response correlation UUID. This is not an idempotency key or receipt address.</p>
<ul class="simple">
@@ -970,7 +1040,7 @@ response correlation UUID. This is not an idempotency key or receipt address.</p
<li><p>405: unsupported HTTP method.</p></li>
<li><p>409: requested query revision changed or revision counter exhausted.</p></li>
<li><p>412: stale write precondition.</p></li>
<li><p>413: body or serialized model exceeds size limit.</p></li>
<li><p>413: body, serialized model, stabilized source bundle or envelope exceeds its size limit.</p></li>
<li><p>415: unsupported Content-Type or Content-Encoding.</p></li>
<li><p>422: rejected source/model/command, model count limit, or missing graph root.</p></li>
<li><p>428: missing If-Match.</p></li>
+89 -13
View File
@@ -12,8 +12,11 @@ Identity, revision and writes
All model routes begin with ``/api/v1``. A hosted project's immutable service
``resource_id`` is different from its editable ``model.id``. Each stored file
contains ``{resource_id, revision, model}``. The revision starts at 1, increments
once per changed accepted transaction, and does not change for a no-op.
contains ``{resource_id, revision, model, source_project}``. ``source_project``
is the accepted entry filename and map of authored source files; compiling it
must reproduce ``model``. The revision starts at 1, increments once per changed
accepted model/source pair, and does not change for an exact no-op. Comment-only
edits and file/entry changes count as changes even when the model is unchanged.
Existing-project writes require the exact quoted ETag returned by a project
read, for example::
@@ -22,7 +25,7 @@ read, for example::
Missing preconditions return 428; stale preconditions return 412. Weak ETags,
wildcards and lists are not accepted on writes. GET of the envelope, model and
source supports ``If-None-Match`` and returns an empty 304 when unchanged.
source/source-project supports ``If-None-Match`` and returns an empty 304 when unchanged.
Use ETags as opaque strings; revisions are unsigned integer counters in JSON.
Every accepted changed write is persisted before the response. A worker captures
@@ -35,7 +38,8 @@ warnings permit work and are returned in ``issues``.
There is no retained revision history, idempotency receipt, tombstone or
server undo. DELETE permanently removes the hosted envelope. Source/JSON
exports contain the current model only. They preserve the model's persistent
interchange exports contain the current model only; source-project export also
preserves the authored file layout and comments. They preserve persistent
aliases, diagrams, artifact references and OA/SA trace data. Artifact revision
strings refer to external artifacts, not past server snapshots.
@@ -81,12 +85,18 @@ bearer token, if any. Non-loopback binding requires a configured token.
* - POST
- ``/api/v1/projects/import``
- Raw model JSON or Arcadia source; new service ID.
* - POST
- ``/api/v1/projects/import-source-project``
- Complete source bundle as JSON; stabilize missing IDs and create a resource.
* - GET / PUT
- ``/api/v1/projects/{id}/model``
- Export or conditionally replace native model JSON.
* - GET / PUT
- ``/api/v1/projects/{id}/source``
- Export or conditionally replace canonical UTF-8 source.
* - GET / PUT
- ``/api/v1/projects/{id}/source-project``
- Export exact authored files or conditionally replace the complete bundle.
* - GET
- ``/api/v1/projects/{id}/elements``
- Filter by ``kind``, ``layer``, ``alias``, name ``q``.
@@ -119,7 +129,10 @@ bearer token, if any. Non-loopback binding requires a configured token.
``text/plain; charset=utf-8`` is used for Arcadia source. The service does not
guess formats from a filename. Unknown DTO fields, invalid UUIDs/enums, duplicate
model map keys and unsupported schemas are rejected. Maximum body size is
8 MiB. Compressed or encoded request bodies and browser Origin requests are
8 MiB. Accepted serialized model JSON and serialized source-bundle JSON each
have an 8 MiB cap, including IDs inserted during compilation. The stored
envelope allows both bounded representations plus small framing overhead.
Compressed or encoded request bodies and browser Origin requests are
not supported.
Model admission permits at most 5,000 elements plus relations, 20,000 total
@@ -134,10 +147,60 @@ does not hold the catalog lock or serialize unrelated model preparation.
Import accepts schema-1 JSON through the shared explicit migration adapter,
as well as schema-2 native JSON. Source import uses the shared language parser
and its supported migrations. Export and native JSON replacement use schema 2.
and its supported migrations. Export uses schema 3 / arcadia 3. Native JSON
replacement accepts supported older schemas through the same explicit migration.
Only documented query parameters are accepted. In particular, appending
``?revision=...`` to model/source export is an error, not historical retrieval.
Authored source projects
------------------------
A source bundle contains ``entry`` and a map of ``files``. For example::
{
"entry": "rescue.arcadia",
"files": {
"rescue.arcadia": "arcadia 3; project rescue \"Rescue\" { include \"oa/work.arcadia\"; }\n",
"oa/work.arcadia": "// Engineering rationale\noa_activity assess \"Assess assistance\" {}\n"
}
}
The entry is a root filename. All keys are canonical, portable, slash-separated
relative paths ending in ``.arcadia``. Includes resolve only against these
submitted strings. They never read the service filesystem, URLs, artifact
targets or another hosted resource. All submitted files must be reachable;
missing files, cycles, duplicate keys, case collisions and escaping paths are
rejected. The bundle permits at most 256 files, 32 include edges along a path,
and 1,024 include edges in total. Model and source-byte budgets apply across
the complete bundle, not separately per file. Bundle syntax is source3;
single-file legacy migration remains available through interchange import.
Compilation inserts UUIDs into headers that omit them. Import returns 201 with
Location/ETag; GET source-project retrieves the stabilized accepted bundle.
PUT returns ``revision``, ``changed``, located ``issues`` and that accepted
``source_project``. Preserve the returned IDs before subsequent editing.
With explicit IDs, untouched text, comments, line endings, entry name and module
ownership are preserved exactly. Re-submitting the same accepted bundle is a
no-op. Removing a file means submitting a complete valid bundle without that
file and its includes; no files are created or deleted on the service host.
All typed command, transition, rename and model-interchange writes update this
same accepted source project. Existing declarations and reference comments
follow command identity correspondence, including UUID swaps. Full-model
replacement uses common UUIDs as its correspondence boundary. It preserves
the accepted source layout; canonical interchange formatting/comments are not
adopted as a replacement authored workspace. Use source-project PUT for
authoring file layout and comments. A failed source writeback rejects the whole
write and never falls back to discarding authored files.
Old model-only envelopes are migrated to a deterministic source bundle in
memory when opened. Reading or an exact no-op does not rewrite their files or
advance revisions. Their next changed write persists both representations.
Startup rejects already-coupled envelopes whose source/model disagree or whose
source lacks stable IDs. The 256 MiB store budget charges complete accepted
envelopes, including the generated source of old envelopes before their next
save; an over-capacity old store is reported rather than silently pruned.
Queries and pagination
----------------------
@@ -217,12 +280,15 @@ creation identities allow later commands in the batch to reference earlier
creations. The shared ``execute_batch`` applies all operations to one candidate
and validates the final state. A rejected operation or final structural error
rejects the whole request. Reference rewrites/deletion cleanup follow the same
core logic as desktop editing.
core logic as desktop editing. Command preparation retains original-to-candidate
identity correspondence through UUID edits, swaps, deletion and reuse. Source
writeback uses that correspondence to preserve declaration ownership and comments.
Dry run still requires ``If-Match``. Its response includes ``revision``,
``dry_run``, ``changed``, ``issues`` and the proposed ``model``. It neither writes
nor reserves IDs. A committed response has the same fields except the model is
omitted. Commit must check the revision again. A no-op is successful with
``dry_run``, ``changed``, ``issues`` and the proposed ``model`` and
``source_project``. It validates source writeback as well as semantics and neither writes
nor reserves IDs. A committed response omits the proposed model and source
project. Commit must check the revision again. A no-op is successful with
``changed: false`` and the original ETag.
Contextual process and chain topology
@@ -287,10 +353,17 @@ or::
{"format":"arcadia", "source":"complete source document"}
or::
{"format":"source-project", "source_project":{"entry":"rescue.arcadia", "files":{"rescue.arcadia":"arcadia 3; project rescue \"Rescue\" {}"}}}
A well-formed validation request returns 200 with ``valid`` and ``issues`` even
if its candidate is invalid. A malformed request envelope or JSON model record
is a 400. Source diagnostics include one-based line and column according to the
shared language parser. Candidate validation does not replace current state.
Source-project validation locations refer to the submitted bytes before UUID
insertion. Successful source-project PUT diagnostics refer to the returned
accepted bundle. Missing IDs are not saved or adopted by validation.
Typed state machines and events
-------------------------------
@@ -327,7 +400,8 @@ Preview accepts the shared ``TransitionRequest`` record, including direction
conflict resolutions and optional destination diagram. All fields are explicit
in OpenAPI. At most 100 sources, 200 mappings and 400 field resolutions are
accepted. A conflict-free plan must fit the same 200-command transaction limit
and model-capacity limits.
and model/source-capacity limits. A conflict-free preview also checks source
writeback, so its commands can update the same accepted authored workspace.
The preview response is ``{"base_revision":1,"plan":{...}}``. It contains
planned changes, conflicts, warnings and exact commands/IDs. It does not write
@@ -357,7 +431,9 @@ All errors have ``Content-Type: application/problem+json``. Example::
}
``issues`` contain severity, code and message, and may include field,
object_id, line and column. Use
object_id, line, column, source ``path``, half-open UTF-8 byte ``span``
(``start``, ``end``), and ``related`` source locations (path/span). Lines and
columns are one-based; columns count Unicode scalars. Use
``code`` for client decisions, not the English message. ``X-Request-ID`` is a
response correlation UUID. This is not an idempotency key or receipt address.
@@ -368,7 +444,7 @@ response correlation UUID. This is not an idempotency key or receipt address.
* 405: unsupported HTTP method.
* 409: requested query revision changed or revision counter exhausted.
* 412: stale write precondition.
* 413: body or serialized model exceeds size limit.
* 413: body, serialized model, stabilized source bundle or envelope exceeds its size limit.
* 415: unsupported Content-Type or Content-Encoding.
* 422: rejected source/model/command, model count limit, or missing graph root.
* 428: missing If-Match.
+9 -7
View File
@@ -8,9 +8,9 @@ The normative contract is [language.rst](language.rst); [scope.md](scope.md) exp
|---|---|---|
| `oa-core` | Model, structural/completeness rules, commands/history, reference maintenance, diagram projection, OA/SA transitions, text and file persistence | Iced, renderer, HTTP or native UI |
| `oa-desktop` | Iced events/state, property and source drafts, graphical views, file interactions | Server availability or an account |
| `oa-server` | HTTP contract, authentication/admission, resource revisions and atomic current-model storage | Iced or a desktop session |
| `oa-server` | HTTP contract, authentication/admission, resource revisions and atomic model/source storage | Iced or a desktop session |
The server reuses the same core library as the desktop. A new automation endpoint must produce a core command/candidate rather than implement a second version of domain rules. Desktop and server are separate storage clients: there is currently no desktop HTTP adapter. Canonical source/JSON import/export is the explicit integration path.
The server reuses the same core library as the desktop. A new automation endpoint must produce a core command/candidate rather than implement a second version of domain rules. Desktop and server are separate storage clients: there is currently no desktop HTTP adapter. Explicit transfer supports canonical model interchange and authored source bundles.
## Model, identity and references
@@ -26,7 +26,7 @@ Element kind and layer derive from closed OA/SA bodies. Shared typed bodies repr
`validation.rs` separates structural errors from incomplete-model warnings. Dangling references, wrong kinds/layers, ownership cycles, incompatible ports and invalid uniqueness reject a candidate. Missing allocations, incomplete references and coverage gaps allow continued authoring. Small predicates support parent/relationship choices; they must agree with the committed-model policy.
The desktop retains up to 100 full-model history snapshots and compares against a saved snapshot to compute dirty state. This favors straightforward correctness for modest models. The cost is proportional to model size; indexed validation, persistent collections or delta history should follow profiling, not speculative infrastructure.
`prepare_command` returns a validated candidate, selection and original-record identity map without committing. Desktop `document.rs` then updates the accepted source through the shared writer before adopting one compiled model/source snapshot. It retains up to 100 accepted changes within an estimated 32 MiB history budget and compares the complete source bundle with its saved baseline. Comment-only changes have history; invalid updates retain the current snapshot. Exact snapshots preserve authored omissions and comments through Undo/Redo. Persistent collections or delta history should follow profiling, not speculative infrastructure.
## Nesting and graphical views
@@ -44,17 +44,19 @@ Keep domain mapping choices in the core or derive UI choices from its queries. I
## Text and draft state
`language.rs` provides a lexer/parser, deterministic formatter, tolerant source analysis and explicit V1 adapters. It collects declaration aliases before resolving UUID references, accepts forward references, rejects unknown/duplicate fields and typed-constructor errors, and validates the complete candidate. Canonical `arcadia 2` text preserves every persistent field, alias, UUID and ordered occurrence. Comments/whitespace are lexical trivia and normalize on Apply. V1 migration preserves existing identities and derives new placement/occurrence IDs deterministically.
`language.rs` provides the shared lexer/parser, deterministic formatter, tolerant source analysis and explicit V1/V2 migration readers. It collects global declaration aliases before resolving UUID references, rejects unknown/duplicate fields and typed-constructor errors, and validates the complete candidate. Source3 elements may nest to express semantic parenthood; iterative parsing retains real lexical-owner/body/own-field spans. Canonical flat output remains available and preserves every model field, alias, UUID and ordered occurrence.
`source_project.rs` compiles an explicit virtual bundle with relative includes, global identity and per-file diagnostics. It never reads a filesystem or network. The accepted bundle inserts missing identities while preserving authored bytes. The shared updater changes individual tokens and keyed list entries, then `source_project/source_structure.rs` composes structural moves from original declaration provenance. Nested reparenting moves attached comments and children across files; flat source organization stays flat. Recompilation and full model equality are mandatory before adoption. Canonical export alone normalizes comments/formatting.
`forms.rs` is a closed adapter over typed records. JSON values are temporary form structure; incomplete numbers, references and escaped strings stay in drafts until decoding a typed Command. This is not an extensible runtime schema or an alternate persisted metamodel. Every added field needs a usable control and meaningful authoring coverage; a no-op serialization test alone cannot establish graphical reachability.
`app.rs` coordinates the Session, selection, draft, source buffer/history, active view/camera, workflow dialogs and file state. `views.rs` renders task navigation and inspectors; `workflow.rs` handles explicit relationship/manual-trace/counterpart choices. `highlight.rs` translates tolerant core tokens to source coloring. Source lint runs after a short debounce and ignores obsolete results. Valid Apply commits once; rejected text stays editable with located diagnostics. Text-buffer history is distinct from committed-model history.
`app.rs` coordinates the accepted Document, selection, property draft, per-file source workspace, active view/camera, workflow dialogs and file state. `views.rs` renders task navigation and inspectors; `workflow.rs` handles explicit relationship/manual-trace/counterpart choices. `highlight.rs` translates tolerant core tokens to source coloring. Lint runs on a worker after a short debounce and ignores obsolete results. Valid Apply commits all reachable files once; invalid or inactive-file drafts retain located diagnostics. Per-buffer history is distinct from accepted model/source history.
## File and service persistence
Core persistence detects JSON by content and otherwise parses Arcadia text. `.arcadia` saves canonical text; other suffixes save strict JSON. A validated snapshot is written to a sibling temporary file, flushed and atomically installed. Normal desktop saves compare the previously read bytes to detect external changes before replacement. This is an optimistic local-file check, not a guarantee against every concurrent writer or power-loss scenario. File tasks run on blocking workers; the desktop preserves drafts and pending New/Open/Close continuations on failure or cancellation.
Core model persistence detects JSON by content and otherwise parses standalone Arcadia text; its model export uses canonical source or strict JSON. Native source-project loading/save uses explicit file receipts, validates physical path ownership and preserves comments across all included files. Save All compares previously read bytes, stages replacements and records an interrupted-save journal. Each replacement is atomic; multi-file visibility is not. Recovery is explicit and survives invalid drafts or missing entry files. File tasks run on blocking workers and failures preserve accepted models, drafts and pending continuations.
The service stores one `{resource_id, revision, model}` envelope per current hosted project. Service identity is immutable and independent of editable model identity. A directory lock enforces one local owner; a store mutex serializes revision checks and writes. Disk replacement precedes in-memory adoption. Authentication, body/admission limits, object/query limits and total store capacity are explicit. No distributed database, retained history, idempotency ledger or automatic artifact fetching is hidden behind this interface. See [server.md](server.md) and [api.rst](api.rst) for deployment, error and concurrency behavior.
The service stores one `{resource_id, revision, model, source_project}` envelope per current hosted project. One ETag protects both representations, including comment-only edits. Every command/transition uses shared source writeback and identity provenance before commit. Old model-only envelopes gain deterministic source in memory without changing disk or revision; inconsistent paired envelopes reject startup. Service identity stays independent of model identity. A directory lock enforces one local owner; a mutex rechecks the ETag and writes the complete envelope before in-memory adoption. Admission charges both model and source. See [server.md](server.md) and [api.rst](api.rst) for limits, errors and deployment.
## Extending a supported concept
+78 -23
View File
@@ -690,7 +690,9 @@ subnormal coordinates are retained.</p>
<h2>3. Element declarations and typed fields</h2>
<p>Each element declaration starts with its layer and kind, for example
<span class="docutils literal">oa_activity</span> or <span class="docutils literal">sa_function</span>. Every element body accepts
<span class="docutils literal">description</span> (default <span class="docutils literal">&quot;&quot;</span>) and <span class="docutils literal">parent</span> (default <span class="docutils literal">none</span>). The table
<span class="docutils literal">description</span> (default <span class="docutils literal">&quot;&quot;</span>) and <span class="docutils literal">parent</span> (default <span class="docutils literal">none</span> for a
document-level declaration, or its lexical element owner when nested; section
6.2). The table
lists all additional fields. <span class="docutils literal">ref</span> means <span class="docutils literal">&#64;alias</span>; <span class="docutils literal">ref?</span> also accepts
<span class="docutils literal">none</span>. Lists preserve order. Unknown or duplicate fields are errors.</p>
<table>
@@ -1450,14 +1452,18 @@ activity, capability, process, allocation, interaction, contribution,
realization, position, steps, true, false, null. Other field/constructor
spellings can be used in alias positions because references begin with <span class="docutils literal">&#64;</span>.</p>
<p>The grammar below uses <span class="docutils literal">*</span> for repetition and <span class="docutils literal">?</span> for optional syntax.
<span class="docutils literal">kind</span> is exactly an element/relation keyword listed above. Typed field
<span class="docutils literal"><span class="pre">element-kind</span></span> and <span class="docutils literal"><span class="pre">relation-kind</span></span> are exactly the corresponding declaration
keywords listed above. Typed field
sets and enum constructors are closed by sections 3–5.</p>
<pre class="code text literal-block"><code>document = &quot;arcadia&quot; &quot;3&quot; &quot;;&quot; project
project = &quot;project&quot; alias string identity? &quot;{&quot; project-entry* &quot;}&quot;
project-entry = &quot;description&quot; string &quot;;&quot; | declaration
declaration = (kind | &quot;diagram&quot; | &quot;artifact&quot; | &quot;trace&quot;)
declaration = element
| (relation-kind | &quot;diagram&quot; | &quot;artifact&quot; | &quot;trace&quot;)
alias string identity? fields
| &quot;node&quot; alias identity? fields
element = element-kind alias string identity? element-body
element-body = &quot;{&quot; (field-name value &quot;;&quot; | element)* &quot;}&quot;
identity = &quot;id&quot; uuid-string
fields = &quot;{&quot; (field-name value &quot;;&quot;)* &quot;}&quot;
value = string | number | &quot;true&quot; | &quot;false&quot; | &quot;none&quot;
@@ -1469,7 +1475,7 @@ record ends with a semicolon, including fields whose values are nested records.
Trailing list commas are invalid. Fields may appear in any order.</p>
<p>UUID strings use 8-4-4-4-12 hexadecimal spelling, with uppercase or lowercase
hexadecimal accepted. Canonical output uses lowercase. Omitting an identity
creates a fresh UUID for that parse. After Apply, canonical text includes the
creates a fresh UUID for that parse. After Apply, accepted source includes the
generated identity. Authors must keep identity clauses to preserve existing
objects across subsequent edits.</p>
<p>All declaration aliases are collected before references resolve, so forward
@@ -1493,8 +1499,8 @@ deeply nested drafts produce a diagnostic instead of exhausting the parser stack
root-level <span class="docutils literal">.arcadia</span> file with the source3 project header above. A project entry
MAY also be <span class="docutils literal">include string &quot;;&quot;</span>. An included fragment consists only of ordinary
declarations and include statements; it MUST NOT contain another version header,
project wrapper, or project description. Includes MUST occur between declarations,
never inside a typed field value. For example:</p>
project wrapper, or project description. Includes MUST occur at document level,
never inside an element body or typed field value. For example:</p>
<pre class="literal-block">arcadia 3;
project rescue &quot;Rescue&quot; id &quot;00000000-0000-4000-8000-000000000001&quot; {
description &quot;Operational model&quot;;
@@ -1514,7 +1520,7 @@ spelling; filesystems do not participate in resolution.</p>
includes. Include cycles are errors with their chain and including location.
Missing files, duplicate JSON file keys and supplied unreachable files are
errors. The compiler MUST NOT read local files, network resources, environment
variables or the process working directory. A future native loader is responsible
variables or the process working directory. The native loader is responsible
for translating a directory into this explicit bundle and enforcing physical
path ownership; the pure compiler grants no filesystem access.</p>
<p><span class="docutils literal">inspect_includes(path, source, is_entry, limits)</span> supports native discovery
@@ -1547,8 +1553,8 @@ input. The returned source index and diagnostics address the returned stabilized
bytes. Clients MUST retain that bundle after successful Apply; recompiling the
original buffers with missing identities would generate fresh identities.</p>
<p>Bundle diagnostics wrap the existing Diagnostic with a logical path and optional
related locations. Declaration spans, top-level field value spans, project
header/body/description spans and resolved include locations are available in
related locations. Declaration spans, own-field value spans, lexical owners,
declaration bodies, project header/body/description spans and resolved includes are available in
the source index. Columns remain one-based Unicode scalars within the real file;
spans never refer to concatenated or synthetic source.</p>
<p>Single-string parse/lint APIs remain filesystem-free and reject include
@@ -1557,6 +1563,36 @@ continue through their existing migration readers and reports. Modular projects
require source3; migration is performed before modular export, not independently
on included fragments. Modular source does not change the semantic JSON schema.</p>
</section>
<section id="nested-element-ownership">
<h3>6.2. Nested element ownership</h3>
<p>An element body MAY contain element declarations interleaved with its ordinary
fields. Its immediate enclosing element is its lexical owner. For example:</p>
<pre class="literal-block">sa_actor harbor &quot;Harbor operator&quot; {
sa_port radio &quot;Radio&quot; {
contract { Component { kind Standard; orientation Unset; }; };
}
}</pre>
<p>Nesting expresses semantic <span class="docutils literal">Element.parent</span> only. It does not allocate a
function to an actor or System, deploy equipment, or add or reorder occurrence
references in an owner's explicit <span class="docutils literal">steps</span> list. Those relationships retain
their explicit records and fields. The
same parent-kind, layer and acyclicity constraints apply as for flat source.
Only element declarations can nest: relations, diagrams, placements, artifacts,
traces and include directives remain at document level. A typed record value
contains fields, never element declarations.</p>
<p>If a nested declaration omits <span class="docutils literal">parent</span>, it inherits its lexical owner's UUID.
An explicit <span class="docutils literal">parent</span> MUST reference that owner; another reference or <span class="docutils literal">none</span>
is an error located at the parent value. At document level, omitted <span class="docutils literal">parent</span>
means <span class="docutils literal">none</span> and explicit parent references retain their existing meaning.
Nesting creates no alias namespace. Aliases and UUIDs remain globally unique,
and references can target nested declarations before or after their definition
or from another included file. Identity stabilization inserts missing UUIDs in
nested headers just as it does in document-level headers.</p>
<p>Source positions distinguish each declaration's own fields from its descendants'
fields. A declaration's body span encloses its lexical children as well as its
own fields. An inherited parent has no synthetic source token or field span.
Located diagnostics and editor navigation MUST address real source bytes.</p>
</section>
</section>
<section id="canonical-output-and-source-tooling">
<h2>7. Canonical output and source tooling</h2>
@@ -1574,11 +1610,10 @@ relations; diagrams and their nodes follow, then artifacts, then traces.
Fields use deterministic name order. Output ends with LF. Alias values come
from the model, never from regenerating display names.</p>
<p>Comments and input whitespace are lexical trivia to the canonical writer.
Canonical export and the current desktop single-document Apply path do not
preserve them; the editor MUST explain that behavior. This existing path does not
define an acceptable lossless authoring policy for a future project editor.
The pure project compiler in section 6.1 preserves them while inserting missing
identities. Project update/export distinctions are described below.
Canonical export does not preserve them. The source-project editor and compiler
preserve authored comments and whitespace on Apply while inserting missing
identities. Explicit canonical export remains a separate operation. Project
update/export distinctions are described below.
Camera position, selection, source cursor, and undo history are not stored
model fields. Layout and collapse state inside NodePlacement are model data.</p>
<p>The pure <span class="docutils literal">format_bundle</span> exporter uses the same typed declaration writer as
@@ -1594,12 +1629,24 @@ For every valid P, <span class="docutils literal"><span class="pre">compile_bund
when the caller supplies sufficient admission budgets.</p>
<p><span class="docutils literal">update_bundle(compiled, P, limits)</span> is a core source update facility. It
checks that the compiled document/index/model snapshot still agrees, preserves
existing declaration file ownership and all untouched document bytes, and
recompiles the result before returning it. New children inherit their owner's
file; new roots use canonical module rules and add an explicit entry include.
untouched document bytes, and recompiles the result before returning it. Flat
declarations retain their document-level position and file when reparented.
Nested declarations move into their new semantic owner's body, across files if
necessary; becoming a root moves them to document level. New children prefer
their owner's body. New roots use canonical module rules and add an explicit
entry include.
Removed declarations do not delete source files. Alias changes also update
declarations containing references to that alias. Invalid or stale updates
return errors without mutating the input workspace.</p>
<p>A moved declaration carries its contiguous leading comment block and comments
following its closing brace on the same line. A blank line separates a comment
block from the following declaration; such gap comments stay in the enclosing
scope. Comments trailing a preceding field likewise stay with that field.
Deletion retains annotations in the nearest surviving original scope and
separates them from adjacent declarations with blank lines. Surviving descendants
move with an enclosing declaration unless their own semantic parent changes;
independent parent and child moves are resolved together. Leading whitespace
adjusts to the destination scope; other token spelling remains authored.</p>
<p>Updates MUST NOT canonicalize surviving declarations in full. Header, field and
nested value positions are derived from the existing syntax parser. Unchanged
fields retain their exact bytes, including field order, omitted defaults, number
@@ -1623,23 +1670,31 @@ that the result remains valid syntax. Unkeyed or duplicate-key lists use source
positions. Removed-entry comments remain as annotations in the list; newly added
entries receive formatted syntax. These rules preserve comments without silently
attaching new semantic identity to an ambiguous list entry.</p>
<p>Retained declaration UUIDs and source ownership remain unchanged. Records match
by UUID, not display name or alias. The model-only <span class="docutils literal">update_bundle</span> treats an
<p>Retained declaration UUIDs remain unchanged unless explicitly edited. Source
ownership remains unchanged except for the nested relocation rules above.
Records match by UUID, not display name or alias. The model-only <span class="docutils literal">update_bundle</span> treats an
explicit UUID replacement as removal and creation. The desktop instead uses
<span class="docutils literal">update_bundle_with_identity_map</span> with the surviving-record provenance from
<span class="docutils literal">prepare_command</span>: explicit identity edits and sequential identity swaps MUST
preserve each original declaration's owning file and attached source trivia.
preserve each original declaration's owning file and attached source trivia,
unless the same edit changes its nested semantic parent and requires relocation.
New records have no original identity mapping. The mapping MUST be injective,
refer to existing records and preserve record classes. The
result is recompiled, reindexed and checked for equality with the requested
model before return. Source changes needed for undo are workspace history data;
model before return. Equal model values do not imply a no-op if command
provenance removes or replaces a declaration; the resulting model and source
bundle together determine whether the accepted state changed.
Source changes needed for undo are workspace history data;
callers should retain complete compiled workspace snapshots rather than infer
past omission/layout choices from a model-only undo.</p>
<p>Canonical modular export is a distinct, explicit operation and does not promise
original comment/layout preservation. The native editor integrates per-file
drafts and source-project saves as specified below. The pure compiler performs
no filesystem access. REST source endpoints currently exchange a canonical
model; they do not yet exchange the multi-file source bundle.</p>
no filesystem access. REST <span class="docutils literal"><span class="pre">source-project</span></span> endpoints exchange the accepted
bundle with all its files. One revision and ETag protect the model and source
together, including source-only comment edits; stored envelopes contain both.
Canonical <span class="docutils literal">source</span> endpoints remain explicit model interchange. The precise
transport, admission and persistence contract is in <span class="docutils literal">api.rst</span>.</p>
<p><span class="docutils literal">parse</span> returns a complete structurally validated candidate or error
diagnostics and no model. Engineering warnings alone MUST NOT reject a valid
candidate. Parsing may stop at the first syntax/conversion error. Diagnostics
+84 -23
View File
@@ -73,7 +73,9 @@ subnormal coordinates are retained.
Each element declaration starts with its layer and kind, for example
``oa_activity`` or ``sa_function``. Every element body accepts
``description`` (default ``""``) and ``parent`` (default ``none``). The table
``description`` (default ``""``) and ``parent`` (default ``none`` for a
document-level declaration, or its lexical element owner when nested; section
6.2). The table
lists all additional fields. ``ref`` means ``@alias``; ``ref?`` also accepts
``none``. Lists preserve order. Unknown or duplicate fields are errors.
@@ -792,7 +794,8 @@ realization, position, steps, true, false, null. Other field/constructor
spellings can be used in alias positions because references begin with ``@``.
The grammar below uses ``*`` for repetition and ``?`` for optional syntax.
``kind`` is exactly an element/relation keyword listed above. Typed field
``element-kind`` and ``relation-kind`` are exactly the corresponding declaration
keywords listed above. Typed field
sets and enum constructors are closed by sections 3–5.
.. code-block:: text
@@ -800,9 +803,12 @@ sets and enum constructors are closed by sections 3–5.
document = "arcadia" "3" ";" project
project = "project" alias string identity? "{" project-entry* "}"
project-entry = "description" string ";" | declaration
declaration = (kind | "diagram" | "artifact" | "trace")
declaration = element
| (relation-kind | "diagram" | "artifact" | "trace")
alias string identity? fields
| "node" alias identity? fields
element = element-kind alias string identity? element-body
element-body = "{" (field-name value ";" | element)* "}"
identity = "id" uuid-string
fields = "{" (field-name value ";")* "}"
value = string | number | "true" | "false" | "none"
@@ -816,7 +822,7 @@ Trailing list commas are invalid. Fields may appear in any order.
UUID strings use 8-4-4-4-12 hexadecimal spelling, with uppercase or lowercase
hexadecimal accepted. Canonical output uses lowercase. Omitting an identity
creates a fresh UUID for that parse. After Apply, canonical text includes the
creates a fresh UUID for that parse. After Apply, accepted source includes the
generated identity. Authors must keep identity clauses to preserve existing
objects across subsequent edits.
@@ -844,8 +850,8 @@ The pure ``source_project`` core API compiles a virtual source bundle, containin
root-level ``.arcadia`` file with the source3 project header above. A project entry
MAY also be ``include string ";"``. An included fragment consists only of ordinary
declarations and include statements; it MUST NOT contain another version header,
project wrapper, or project description. Includes MUST occur between declarations,
never inside a typed field value. For example::
project wrapper, or project description. Includes MUST occur at document level,
never inside an element body or typed field value. For example::
arcadia 3;
project rescue "Rescue" id "00000000-0000-4000-8000-000000000001" {
@@ -868,7 +874,7 @@ Every reachable file is compiled once, including through repeated or diamond
includes. Include cycles are errors with their chain and including location.
Missing files, duplicate JSON file keys and supplied unreachable files are
errors. The compiler MUST NOT read local files, network resources, environment
variables or the process working directory. A future native loader is responsible
variables or the process working directory. The native loader is responsible
for translating a directory into this explicit bundle and enforcing physical
path ownership; the pure compiler grants no filesystem access.
@@ -906,8 +912,8 @@ bytes. Clients MUST retain that bundle after successful Apply; recompiling the
original buffers with missing identities would generate fresh identities.
Bundle diagnostics wrap the existing Diagnostic with a logical path and optional
related locations. Declaration spans, top-level field value spans, project
header/body/description spans and resolved include locations are available in
related locations. Declaration spans, own-field value spans, lexical owners,
declaration bodies, project header/body/description spans and resolved includes are available in
the source index. Columns remain one-based Unicode scalars within the real file;
spans never refer to concatenated or synthetic source.
@@ -917,6 +923,41 @@ continue through their existing migration readers and reports. Modular projects
require source3; migration is performed before modular export, not independently
on included fragments. Modular source does not change the semantic JSON schema.
6.2. Nested element ownership
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An element body MAY contain element declarations interleaved with its ordinary
fields. Its immediate enclosing element is its lexical owner. For example::
sa_actor harbor "Harbor operator" {
sa_port radio "Radio" {
contract { Component { kind Standard; orientation Unset; }; };
}
}
Nesting expresses semantic ``Element.parent`` only. It does not allocate a
function to an actor or System, deploy equipment, or add or reorder occurrence
references in an owner's explicit ``steps`` list. Those relationships retain
their explicit records and fields. The
same parent-kind, layer and acyclicity constraints apply as for flat source.
Only element declarations can nest: relations, diagrams, placements, artifacts,
traces and include directives remain at document level. A typed record value
contains fields, never element declarations.
If a nested declaration omits ``parent``, it inherits its lexical owner's UUID.
An explicit ``parent`` MUST reference that owner; another reference or ``none``
is an error located at the parent value. At document level, omitted ``parent``
means ``none`` and explicit parent references retain their existing meaning.
Nesting creates no alias namespace. Aliases and UUIDs remain globally unique,
and references can target nested declarations before or after their definition
or from another included file. Identity stabilization inserts missing UUIDs in
nested headers just as it does in document-level headers.
Source positions distinguish each declaration's own fields from its descendants'
fields. A declaration's body span encloses its lexical children as well as its
own fields. An inherited parent has no synthetic source token or field span.
Located diagnostics and editor navigation MUST address real source bytes.
7. Canonical output and source tooling
--------------------------------------
@@ -940,11 +981,10 @@ Fields use deterministic name order. Output ends with LF. Alias values come
from the model, never from regenerating display names.
Comments and input whitespace are lexical trivia to the canonical writer.
Canonical export and the current desktop single-document Apply path do not
preserve them; the editor MUST explain that behavior. This existing path does not
define an acceptable lossless authoring policy for a future project editor.
The pure project compiler in section 6.1 preserves them while inserting missing
identities. Project update/export distinctions are described below.
Canonical export does not preserve them. The source-project editor and compiler
preserve authored comments and whitespace on Apply while inserting missing
identities. Explicit canonical export remains a separate operation. Project
update/export distinctions are described below.
Camera position, selection, source cursor, and undo history are not stored
model fields. Layout and collapse state inside NodePlacement are model data.
@@ -962,13 +1002,26 @@ when the caller supplies sufficient admission budgets.
``update_bundle(compiled, P, limits)`` is a core source update facility. It
checks that the compiled document/index/model snapshot still agrees, preserves
existing declaration file ownership and all untouched document bytes, and
recompiles the result before returning it. New children inherit their owner's
file; new roots use canonical module rules and add an explicit entry include.
untouched document bytes, and recompiles the result before returning it. Flat
declarations retain their document-level position and file when reparented.
Nested declarations move into their new semantic owner's body, across files if
necessary; becoming a root moves them to document level. New children prefer
their owner's body. New roots use canonical module rules and add an explicit
entry include.
Removed declarations do not delete source files. Alias changes also update
declarations containing references to that alias. Invalid or stale updates
return errors without mutating the input workspace.
A moved declaration carries its contiguous leading comment block and comments
following its closing brace on the same line. A blank line separates a comment
block from the following declaration; such gap comments stay in the enclosing
scope. Comments trailing a preceding field likewise stay with that field.
Deletion retains annotations in the nearest surviving original scope and
separates them from adjacent declarations with blank lines. Surviving descendants
move with an enclosing declaration unless their own semantic parent changes;
independent parent and child moves are resolved together. Leading whitespace
adjusts to the destination scope; other token spelling remains authored.
Updates MUST NOT canonicalize surviving declarations in full. Header, field and
nested value positions are derived from the existing syntax parser. Unchanged
fields retain their exact bytes, including field order, omitted defaults, number
@@ -995,24 +1048,32 @@ positions. Removed-entry comments remain as annotations in the list; newly added
entries receive formatted syntax. These rules preserve comments without silently
attaching new semantic identity to an ambiguous list entry.
Retained declaration UUIDs and source ownership remain unchanged. Records match
by UUID, not display name or alias. The model-only ``update_bundle`` treats an
Retained declaration UUIDs remain unchanged unless explicitly edited. Source
ownership remains unchanged except for the nested relocation rules above.
Records match by UUID, not display name or alias. The model-only ``update_bundle`` treats an
explicit UUID replacement as removal and creation. The desktop instead uses
``update_bundle_with_identity_map`` with the surviving-record provenance from
``prepare_command``: explicit identity edits and sequential identity swaps MUST
preserve each original declaration's owning file and attached source trivia.
preserve each original declaration's owning file and attached source trivia,
unless the same edit changes its nested semantic parent and requires relocation.
New records have no original identity mapping. The mapping MUST be injective,
refer to existing records and preserve record classes. The
result is recompiled, reindexed and checked for equality with the requested
model before return. Source changes needed for undo are workspace history data;
model before return. Equal model values do not imply a no-op if command
provenance removes or replaces a declaration; the resulting model and source
bundle together determine whether the accepted state changed.
Source changes needed for undo are workspace history data;
callers should retain complete compiled workspace snapshots rather than infer
past omission/layout choices from a model-only undo.
Canonical modular export is a distinct, explicit operation and does not promise
original comment/layout preservation. The native editor integrates per-file
drafts and source-project saves as specified below. The pure compiler performs
no filesystem access. REST source endpoints currently exchange a canonical
model; they do not yet exchange the multi-file source bundle.
no filesystem access. REST ``source-project`` endpoints exchange the accepted
bundle with all its files. One revision and ETag protect the model and source
together, including source-only comment edits; stored envelopes contain both.
Canonical ``source`` endpoints remain explicit model interchange. The precise
transport, admission and persistence contract is in ``api.rst``.
``parse`` returns a complete structurally validated candidate or error
diagnostics and no model. Engineering warnings alone MUST NOT reject a valid
+374 -25
View File
@@ -3,7 +3,7 @@
"info": {
"title": "Arcadia Studio model API",
"version": "1.0.0",
"description": "Current schema-3 snapshots and conditional atomic writes. Request body limit: 8 MiB; at most 200 nested commands; no retained revisions, request receipts, automatic retries or persistent transition plans. See docs/api.rst. Fixed store capacity: 128 models / 256 MiB serialized envelopes. Stateless transition preview/apply. Artifact URI/revision strings remain opaque core fields. Model admission before structural validation: 5,000 graph objects, 20,000 total records and 50,000 ordered references. Preparation uses snapshots outside the catalog lock; final commits recheck the exact ETag."
"description": "Current schema-3 snapshots and conditional atomic writes. Request body limit: 8 MiB; at most 200 nested commands; no retained revisions, request receipts, automatic retries or persistent transition plans. See docs/api.rst. Fixed store capacity: 128 models / 256 MiB serialized envelopes. Stateless transition preview/apply. Artifact URI/revision strings remain opaque core fields. Model admission before structural validation: 5,000 graph objects, 20,000 total records and 50,000 ordered references. Preparation uses snapshots outside the catalog lock; final commits recheck the exact ETag. Each accepted envelope couples model and authored source under one revision. Source-only edits advance the shared ETag. Source bundles resolve logical paths only, preserve authored files/comments, and have a stabilized JSON cap of 8 MiB, 256 files, 32 include edges along a path and 1,024 include edges overall."
},
"servers": [
{
@@ -700,7 +700,8 @@
}
}
}
}
},
"description": "Replace semantics while preserving the accepted authored source layout and comments, using common UUIDs as identity correspondence. Model and source commit together. Use source-project PUT to replace authored files."
}
},
"/api/v1/projects/{id}/source": {
@@ -759,7 +760,8 @@
"default": {
"$ref": "#/components/responses/Problem"
}
}
},
"description": "Canonical single-file interchange view of the current model. Use source-project GET to export exact authored files and comments."
},
"put": {
"operationId": "replaceSource",
@@ -829,7 +831,8 @@
}
}
}
}
},
"description": "Replace semantics from canonical single-file interchange while preserving the existing authored source bundle using common UUIDs. Submitted formatting/comments do not replace the authored workspace. Use source-project PUT for that purpose."
}
},
"/api/v1/projects/{id}/elements": {
@@ -1403,7 +1406,8 @@
}
}
}
}
},
"description": "Validate native model JSON, canonical Arcadia source, or a complete source-project bundle without adoption. Source-project diagnostics refer to submitted file paths and positions. Stabilized source/model byte limits are also checked."
}
},
"/api/v1/projects/{id}/transactions": {
@@ -1475,7 +1479,8 @@
}
}
}
}
},
"description": "Command identity correspondence drives source writeback, including UUID swaps and delete/recreate operations even when final models compare equal. Dry runs return both proposed representations and validate writeback. Failure preserves the accepted pair."
}
},
"/api/v1/projects/{id}/transitions/preview": {
@@ -1540,7 +1545,8 @@
}
}
}
}
},
"description": "Conflict-free previews also validate authored-source writeback and stabilized model/source capacities."
}
},
"/api/v1/projects/{id}/transitions/apply": {
@@ -1607,6 +1613,190 @@
}
}
}
},
"/api/v1/projects/import-source-project": {
"post": {
"operationId": "importSourceProject",
"summary": "Import an authored source bundle as a new hosted resource",
"parameters": [],
"responses": {
"201": {
"description": "Success",
"headers": {
"ETag": {
"schema": {
"type": "string"
},
"description": "Project revision validator when scoped to a project."
},
"X-Request-ID": {
"schema": {
"type": "string",
"format": "uuid"
}
},
"Location": {
"schema": {
"type": "string"
},
"description": "Path to the created hosted resource."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProjectSummary"
}
}
}
},
"default": {
"$ref": "#/components/responses/Problem"
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SourceBundle"
}
}
}
},
"description": "Compile all logical source files, stabilize missing IDs, and persist the accepted model and bundle together. Importing source3 bundles never reads the service filesystem. Export the accepted bundle after import to retain inserted IDs."
}
},
"/api/v1/projects/{id}/source-project": {
"get": {
"operationId": "getSourceProject",
"summary": "Export the exact accepted authored source bundle",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "Hosted resource UUID; distinct from editable model ID.",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "If-None-Match",
"in": "header",
"required": false,
"description": "Return 304 when the current representation's revision matches.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"headers": {
"ETag": {
"schema": {
"type": "string"
},
"description": "Project revision validator when scoped to a project."
},
"X-Request-ID": {
"schema": {
"type": "string",
"format": "uuid"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SourceBundle"
}
}
}
},
"304": {
"description": "Unchanged; empty body."
},
"default": {
"$ref": "#/components/responses/Problem"
}
},
"description": "Returns entry and all files with stable IDs, original comments, line endings and module layout. Shares the model ETag; comment-only changes advance that revision."
},
"put": {
"operationId": "replaceSourceProject",
"summary": "Conditionally replace the complete authored source bundle",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "Hosted resource UUID; distinct from editable model ID.",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "If-Match",
"in": "header",
"required": true,
"description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"headers": {
"ETag": {
"schema": {
"type": "string"
},
"description": "Project revision validator when scoped to a project."
},
"X-Request-ID": {
"schema": {
"type": "string",
"format": "uuid"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SourceProjectMutationResult"
}
}
}
},
"412": {
"$ref": "#/components/responses/Problem"
},
"428": {
"$ref": "#/components/responses/Problem"
},
"default": {
"$ref": "#/components/responses/Problem"
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SourceBundle"
}
}
}
},
"description": "Compile and validate the full submitted bundle; return accepted text with missing UUIDs inserted. Model and source persist together under the exact If-Match precondition. Comment-only or file-layout edits advance revision; an exact pair no-op retains it. Rejected candidates preserve both representations."
}
}
},
"components": {
@@ -1750,7 +1940,10 @@
"description": "Relative diagram-space displacement."
}
},
"required": ["fraction", "offset"],
"required": [
"fraction",
"offset"
],
"additionalProperties": false
},
"EdgeRoute": {
@@ -1764,20 +1957,28 @@
},
"waypoints": {
"type": "array",
"items": { "$ref": "#/components/schemas/Position" },
"items": {
"$ref": "#/components/schemas/Position"
},
"maxItems": 128,
"default": [],
"description": "Absolute diagram positions in traversal order."
},
"label": {
"oneOf": [
{ "$ref": "#/components/schemas/EdgeLabel" },
{ "type": "null" }
{
"$ref": "#/components/schemas/EdgeLabel"
},
{
"type": "null"
}
],
"default": null
}
},
"required": ["edge"],
"required": [
"edge"
],
"additionalProperties": false
},
"Size": {
@@ -3493,7 +3694,9 @@
},
"routes": {
"type": "array",
"items": { "$ref": "#/components/schemas/EdgeRoute" },
"items": {
"$ref": "#/components/schemas/EdgeRoute"
},
"default": [],
"description": "Unique by edge identity within this diagram. Stored order is retained. Each entry counts against the total model record admission limit. Missing routes in earlier schema-3 models default to empty."
}
@@ -3782,14 +3985,19 @@
}
],
"description": "Present after legacy import/reopen; excluded from persisted envelopes. Resource identity and revision are unchanged by reopening."
},
"source_project": {
"$ref": "#/components/schemas/SourceBundle"
}
},
"required": [
"resource_id",
"revision",
"model"
"model",
"source_project"
],
"additionalProperties": false
"additionalProperties": false,
"description": "Accepted coupled model/source snapshot. Compiling source_project reproduces model; revision changes once if either representation changes. Old model-only envelopes acquire source in memory without a write or revision change."
},
"ProjectSummary": {
"type": "object",
@@ -3866,13 +4074,27 @@
},
"column": {
"type": "integer",
"minimum": 1
"minimum": 1,
"description": "One-based Unicode scalar column."
},
"code": {
"type": "string"
},
"field": {
"type": "string"
},
"path": {
"type": "string",
"description": "Logical source file path, when a source project supplied the diagnostic."
},
"span": {
"$ref": "#/components/schemas/SourceSpan"
},
"related": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SourceLocation"
}
}
},
"required": [
@@ -4464,14 +4686,24 @@
"type": "object",
"description": "Upsert by edge identity; preserves unrelated route entries and order. Uses existing transaction validation and ETag checks.",
"properties": {
"diagram": { "type": "string", "format": "uuid" },
"route": { "$ref": "#/components/schemas/EdgeRoute" }
"diagram": {
"type": "string",
"format": "uuid"
},
"route": {
"$ref": "#/components/schemas/EdgeRoute"
}
},
"required": ["diagram", "route"],
"required": [
"diagram",
"route"
],
"additionalProperties": false
}
},
"required": ["SetEdgeRoute"],
"required": [
"SetEdgeRoute"
],
"additionalProperties": false
},
{
@@ -4481,14 +4713,25 @@
"type": "object",
"description": "Reset to automatic routing. No-op if this existing diagram has no route for the existing edge.",
"properties": {
"diagram": { "type": "string", "format": "uuid" },
"edge": { "type": "string", "format": "uuid" }
"diagram": {
"type": "string",
"format": "uuid"
},
"edge": {
"type": "string",
"format": "uuid"
}
},
"required": ["diagram", "edge"],
"required": [
"diagram",
"edge"
],
"additionalProperties": false
}
},
"required": ["RemoveEdgeRoute"],
"required": [
"RemoveEdgeRoute"
],
"additionalProperties": false
},
{
@@ -4710,7 +4953,12 @@
}
},
"model": {
"$ref": "#/components/schemas/Project"
"$ref": "#/components/schemas/Project",
"description": "Proposed model, present only for a dry run."
},
"source_project": {
"$ref": "#/components/schemas/SourceBundle",
"description": "Proposed accepted bundle, present only for a dry run. Source writeback is validated before returning a successful candidate."
}
},
"required": [
@@ -4788,6 +5036,22 @@
"source"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"format": {
"const": "source-project"
},
"source_project": {
"$ref": "#/components/schemas/SourceBundle"
}
},
"required": [
"format",
"source_project"
],
"additionalProperties": false
}
]
},
@@ -5818,6 +6082,91 @@
],
"additionalProperties": false,
"description": "Transient observations from explicit V1→V2→V3 or V2→V3 migration. No save or revision increase occurs merely by reading an old envelope."
},
"SourceBundle": {
"type": "object",
"description": "Complete source3 workspace. The entry is a root filename. Files use canonical portable slash-separated relative .arcadia paths and all must be reachable. Includes resolve only against this map, never the service filesystem. Up to 256 files, 32 include edges along a path and 1,024 include edges overall. Serialized bundle JSON is at most 8 MiB, checked again after missing UUIDs are inserted. GET returns exact accepted stable text.",
"properties": {
"entry": {
"type": "string"
},
"files": {
"type": "object",
"maxProperties": 256,
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"entry",
"files"
],
"additionalProperties": false
},
"SourceSpan": {
"type": "object",
"description": "Half-open UTF-8 byte offsets [start,end) in the named source file.",
"properties": {
"start": {
"type": "integer",
"minimum": 0
},
"end": {
"type": "integer",
"minimum": 0
}
},
"required": [
"start",
"end"
],
"additionalProperties": false
},
"SourceLocation": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"span": {
"$ref": "#/components/schemas/SourceSpan"
}
},
"required": [
"path",
"span"
],
"additionalProperties": false
},
"SourceProjectMutationResult": {
"type": "object",
"properties": {
"revision": {
"type": "integer",
"minimum": 1,
"description": "Current revision counter; use ETag as the opaque write validator."
},
"changed": {
"type": "boolean"
},
"issues": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Issue"
}
},
"source_project": {
"$ref": "#/components/schemas/SourceBundle"
}
},
"required": [
"revision",
"changed",
"issues",
"source_project"
],
"additionalProperties": false
}
}
}
+15 -7
View File
@@ -1,6 +1,6 @@
# Arcadia Studio server
`arcadia-server` is a headless Rust REST service for the same schema-2 model, commands, source language, validation, artifacts and OA/SA traces used by the desktop. It stores each current project in an atomic JSON envelope. The service provides a deployment artifact and configuration examples; this repository does not assert that a remote instance has been deployed.
`arcadia-server` is a headless Rust REST service for the same schema-3 model, commands, source language, validation, artifacts and OA/SA traces used by the desktop. It stores each model and its authored source project together in an atomic JSON envelope. The service provides a deployment artifact and configuration examples; this repository does not assert that a remote instance has been deployed.
The complete HTTP contract is [api.rst](api.rst); machine-readable schemas are in [openapi.json](openapi.json), also served at `/api/v1/openapi.json`. The original [service research](research-v2-service.md) discusses broader alternatives; retained history, SQLite, idempotency receipts and stored transition plans from that research are **not** the implemented storage contract.
@@ -34,17 +34,19 @@ curl --fail-with-body -i -X POST http://127.0.0.1:8787/api/v1/projects \
-H 'Content-Type: application/json' -d '{"name":"Harbor assistance"}'
```
The create response returns a service `resource_id`, revision and `ETag`. Fetch `/api/v1/projects/{resource_id}` to obtain the full `{resource_id, revision, model}` envelope. Use the exact ETag as `If-Match` on updates. With authentication enabled, add `Authorization: Bearer <your-token>`; avoid putting the token in URLs, shell history or shared example files.
The create response returns a service `resource_id`, revision and `ETag`. Fetch `/api/v1/projects/{resource_id}` to obtain the full `{resource_id, revision, model, source_project}` envelope. Use the exact ETag as `If-Match` on updates. With authentication enabled, add `Authorization: Bearer <your-token>`; avoid putting the token in URLs, shell history or shared example files.
The service resource ID is distinct from the editable model ID. Model IDs, aliases, diagrams, artifact URIs/revisions and trace records round-trip through source/JSON. Imported copies receive new resource IDs. An artifact's `revision` is an opaque external-artifact revision string; it is not a retained server model revision.
## Storage and concurrency
The data directory contains `.arcadia.lock` and one `<resource-id>.json` file per hosted project. Each envelope has exactly `resource_id`, positive `revision` and `model`. The process keeps an exclusive OS file lock on the directory lock file for the entire store lifetime. A second process using the same directory fails at startup. Do not directly edit, synchronize or replace files while the service is running.
The data directory contains `.arcadia.lock` and one `<resource-id>.json` file per hosted project. Each envelope has exactly `resource_id`, positive `revision`, `model` and `source_project`. The source bundle contains its entry filename and a map of file text; compiling it must reproduce the stored model exactly. The process keeps an exclusive OS file lock on the directory lock file for the entire store lifetime. A second process using the same directory fails at startup. Do not directly edit, synchronize or replace files while the service is running.
Startup validates every committed envelope, its filename/UUID, revision, size and complete model. Any corrupt or unsupported envelope stops startup; the service does not silently skip bad data or invent an empty replacement. Sibling `.arcadia-write-*` files from an interrupted pre-commit write are not committed models and are ignored on startup.
Parsing, validation and blocking disk operations run on blocking workers. A shared store mutex captures snapshots and serializes final revision checks and writes. Parsing, semantic validation and query processing use the captured snapshot outside that lock; an expensive candidate does not block the catalog or another model's preparation. Commit rechecks the exact ETag, so a candidate prepared against a superseded snapshot returns `412 revision_conflict`. A successful changed update increments revision once; an unchanged model returns `changed: false` and retains its revision. Each write serializes the whole envelope to a sibling temporary file, syncs it, and atomically replaces the destination. Unix also syncs the directory. The in-memory snapshot changes only after the replacement succeeds. A directory-sync error after replacement is reported as an uncertain write outcome while the in-memory head still matches the installed file.
Earlier model-only envelopes are accepted through the existing model migrations and receive deterministic source files in memory. Opening, reading or an exact no-op neither rewrites those envelopes nor advances their revision. The next changed edit persists the coupled representation. Already-coupled envelopes must contain stable source IDs and matching model/source; inconsistent pairs are rejected at startup. Capacity accounting includes source generated in memory for old envelopes, so an over-capacity old store fails explicitly without rewriting or pruning it.
Parsing, validation and blocking disk operations run on blocking workers. A shared store mutex captures snapshots and serializes final revision checks and writes. Parsing, semantic validation, source writeback and query processing use the captured snapshot outside that lock; an expensive candidate does not block the catalog or another model's preparation. Commit rechecks the exact ETag, so a candidate prepared against a superseded snapshot returns `412 revision_conflict`. A successful changed update increments revision once. Both model and source bundle must be unchanged for `changed: false`; comment-only edits advance the same revision. Each write serializes the whole envelope to a sibling temporary file, syncs it, and atomically replaces the destination. Unix also syncs the directory. The in-memory snapshot changes only after the replacement succeeds. A directory-sync error after replacement is reported as an uncertain write outcome while the in-memory head still matches the installed file.
Atomic replacement protects against partially written envelopes. It does not promise survival of every device/filesystem power-loss failure, protect against out-of-band edits, or coordinate OneDrive/network filesystems. The storage directory must be local to this one process. DELETE permanently removes the envelope; there is no recycle bin, tombstone or server undo history.
@@ -54,14 +56,15 @@ Back up by stopping the server cleanly and copying committed JSON envelopes to a
## Limits and errors
- Raw request bodies: 8 MiB, including requests without `Content-Length`. Encoded/compressed request bodies are rejected. The same limit applies to serialized model JSON; the storage envelope permits small framing overhead.
- Raw request bodies: 8 MiB, including requests without `Content-Length`. Encoded/compressed request bodies are rejected. Serialized model JSON and serialized source-bundle JSON each have an 8 MiB cap. The latter is checked again after omitted IDs are inserted. The storage envelope allows both representations plus small framing overhead.
- Source bundles contain at most 256 files, with at most 32 include edges along a path and 1,024 include edges overall. Includes resolve only against submitted logical `.arcadia` paths; no server filesystem or network access is involved. All files must be reachable, and cycles, duplicate keys, case collisions and escaping paths are rejected.
- At most eight admitted model requests and eight active/waiting blocking workers. Overload returns `503` with `Retry-After: 1`. A 30-second request deadline bounds body receipt and response waiting. A started blocking write can finish after that deadline; `504 outcome_unknown` means reload before retrying.
- At most 5,000 elements plus relations, 20,000 total persistent model records, and 50,000 ordered step references per model. Graph requests allow 1–100 roots, depth 0–4, and at most 1,000 returned objects. List pages contain 1–1,000 records, default 100.
- Source declarations and decoded JSON records are counted before structural validation. V1 import includes the occurrences and placements migration will generate in its early budget check. These limits apply to import, source/model replacement, candidate validation and full-model transaction commands. Over-budget candidates cannot monopolize the catalog while being rejected.
- Transactions accept at most 200 commands, including nested batch wrappers. All commands apply to a candidate, then shared validation accepts or rejects the batch atomically. Dry run returns the candidate and diagnostics without writing.
- Transactions accept at most 200 commands, including nested batch wrappers. All commands apply to a candidate, then shared validation and authored-source writeback accept or reject the batch atomically. Dry run returns the proposed model, source project and diagnostics without writing.
- The fixed store cap is 128 hosted models and 256 MiB of serialized envelopes. New/imported/replaced models that exceed it return `507 store_capacity`. Startup enforces the same limits. Delete/export unneeded projects before adding more data; there are no hidden eviction or pruning rules. The server stores all current models in memory; memory includes Rust object overhead and in-flight candidates in addition to serialized bytes.
Errors use `application/problem+json` with stable `code`, HTTP `status`, human `detail`, and contextual `issues` when available. Responses carry `X-Request-ID`; configured bearer credentials and source/model bodies are not logged. The core currently supplies human command errors; the API maps them to `422 command_rejected` without parsing English messages into invented machine codes. Source errors preserve line/column diagnostics.
Errors use `application/problem+json` with stable `code`, HTTP `status`, human `detail`, and contextual `issues` when available. Responses carry `X-Request-ID`; configured bearer credentials and source/model bodies are not logged. The core currently supplies human command errors; the API maps them to `422 command_rejected` without parsing English messages into invented machine codes. Source-project errors preserve logical file paths, UTF-8 byte spans, line/column and related-file locations.
## Linux deployment
@@ -86,6 +89,10 @@ Ctrl+C/SIGTERM stops admission and drains HTTP requests. Already-started blockin
A desktop or automation client can query current JSON/source, edit with shared typed commands, validate/dry-run, and commit using the original ETag. For hosted editing, keep the user's draft until the service acknowledges the new revision. On `412`, preserve the draft and reload server state for review; a blind replacement retry would discard another client's work.
Use `POST /api/v1/projects/import-source-project` to import `{"entry":"model.arcadia","files":{"model.arcadia":"..."}}`. Use `GET` and conditional `PUT /api/v1/projects/{id}/source-project` to export or edit that complete authored workspace. These routes preserve comments, line endings, entry names and module layout. Missing declaration IDs are inserted once; keep the accepted bundle returned by PUT or subsequent GET. Candidate validation accepts `{"format":"source-project","source_project":{...}}` and reports diagnostics without adoption.
Typed commands, renames and transitions update the same authored files using command identity correspondence, including UUID swaps and delete/recreate operations. Model JSON and canonical single-file source replacements preserve the existing authored layout using common UUIDs. Canonical `/source` remains an interchange view; use `/source-project` to retain or edit file layout and comments. Source writeback failure rejects the whole candidate instead of discarding the authored files. Comment-only edits share the same ETag as semantic edits, preventing one client from overwriting another client's source work.
External tools can use the artifact-link and trace query routes, and create/edit those records in the same atomic transaction as model changes. URI and external revision fields preserve the author's values; the service supplies no fetching, credentials, artifact-content verification or AI execution runtime. OpenAPI is the integration contract. An optional future adapter can call this API without introducing a second mutation path.
`POST /projects/{id}/transitions/preview` produces a shared core transition plan and `base_revision` without storing a plan or changing the model. Send that response to `/transitions/apply` with the original ETag. Apply checks the server revision and the core's model-change fingerprint, preserves planned IDs, validates the candidate, and commits once. Plans with unresolved conflicts cannot apply. A submitted plan is a client-owned command proposal, not a signed server authorization; a client able to apply a plan already has the same write permission as the transaction endpoint.
@@ -102,6 +109,7 @@ The HTTP integration suite starts real loopback listeners over isolated temporar
Verified on 2026-09-05:
- Windows authored-source service extension: nine new real HTTP tests and one OpenAPI contract test pass, covering exact multi-file/comment round trips, comment-only ETags, ID insertion, source/model parity, command provenance through UUID swaps and equal-model delete/recreate, dry runs, transitions, invalid dependencies, concurrent writes, restart migration and forced source-write failure. The source-byte accounting unit passes; the focused `source_` regression selection passes all 14 HTTP/schema tests. Strict server all-targets Clippy passes. These are local Windows results; Linux and remote deployment remain separate checks.
- Windows R1/R2/R4 source/API integration: all 16 current HTTP/configuration tests plus the deterministic commit test pass; strict server all-targets Clippy passes. New cases cover mission maps, branching/reused chains, typed call value edits, original V2 compatibility, source/JSON equality and persistence after restart. See `harness/release-language-integration.md`; native UI and remaining release semantics are separate gates.
- Windows R2 release checkpoint: all 14 server integration/configuration tests plus one deterministic commit test pass; formatting and strict all-targets Clippy pass. A separate optimized executable probe rejects the previously stalling 12,000-activity request in 0.050 seconds; concurrent JSON import and accepted-source preparation leave catalog reads responsive. See `harness/release-text-service-audit.md` for exact measurements and scope.
- Ubuntu 24.04 under WSL, prior V2 baseline: all 12 then-existing tests passed using native Linux processes and temporary filesystem stores; strict all-targets Clippy and the optimized build passed with the locked, vendored dependency set. A separate release-executable smoke run verified bearer authentication, non-loopback token enforcement, exclusive directory locking, conditional create/update/delete, update and deletion persistence across restarts, served OpenAPI, and clean SIGTERM shutdown. The new first-release changes still require native Linux re-verification.