Skip to content

Design: per-app launcher descriptors migrate to .deed — the filename is a closed set of four, and this is a rewrite not a rename (standards#960 AC2) #42

Description

@hyperpolymath

⚠ This body was rewritten 2026-09-22 after a citation error. The first version cited deed/spec/abnf/deed.abnf, which is a stale v0.1.0 DRAFT sitting in a peer branch's working tree. The sole normative grammar is 1-formats/deed/spec/abnf/deed.abnf (v1.0.0) on standards origin/main. The conclusion did not change, but two constraints below were missed the first time and both matter. See the comment for the diff.

Context

hyperpolymath/standards#960 AC2 asked whether the 21 per-app launcher descriptors (*.launcher.a2ml) should migrate to .deed or be declared out of scope for D73-C. The owner has ruled: migrate to .deed (2026-09-22, recorded at standards#960).

AC2 is closed by that decision — its wording is "decided, not left to decay". This issue is the design work the decision creates. It is deliberately not a migration PR: it sequences behind #40 (the @a2ml-metadata compat reader), under the dual-accept rule — do not migrate extensions before dual-accept, and a dry-run manifest first, never a bulk pass.

All citations below are to hyperpolymath/standards origin/main.


Question 1 — the filename. <app>.launcher.deed is not a legal deed name.

Deed filenames are a normatively closed production. 1-formats/deed/spec/abnf/deed.abnf:66-71:

deed-filename = estate-file / atlas-file / praxis-file / repo-file
estate-file   = %s"estate_chora.deed"     ; -> estate-deed
atlas-file    = %s"ATLAS.deed"            ; -> estate-atlas-deed
praxis-file   = stem %s"_praxis.deed"     ; -> praxis-deed
repo-file     = stem %s"_chora.deed"      ; -> repo-deed
stem          = 1*( ALPHA / DIGIT / "-" / "." / "_" )

with the matching closed head set at deed.abnf:46 and DEED-GRAMMAR-SPEC.adoc:192, and a semantic constraint at deed.abnf:50-56: "The doc-head MUST match the filename dispatch … A mismatch is a validation error."

Filename dispatch is normative in its own right — the spec says outright that "the grammar alone is not sufficient to dispatch a filename" (DEED-GRAMMAR-SPEC.adoc:310-315). So <app>.launcher.deed, matching none of the four productions, is not a deed.

The stem→head table (DEED-GRAMMAR-SPEC.adoc:276-295) is the authority on meaning:

pattern head meaning
estate_chora.deed (exact) estate-deed Noun. The estate's vocabulary.
*_chora.deed excl. the above repo-deed Noun. What a repo IS. A record.
ATLAS.deed (exact) estate-atlas-deed Noun. The registry of all deeds.
*_praxis.deed praxis-deed Verb. What a tool DOES. Rules.

⭐ Arm B — <app>.launcher_praxis.deed (recommended)

A stem may contain dots. deed.abnf:71 admits . in stem, and deed.abnf:62-64 says so explicitly: "Do NOT split on . — the stem may contain dots (my.project_chora.deed → stem my.project)." (Mirrored at DEED-GRAMMAR-SPEC.adoc:297-299.)

So myapp.launcher_praxis.deed is a legal deed filename, stem myapp.launcher, dispatching to praxis-deed. That matters: the rename is a suffix swap, .launcher.a2ml → .launcher_praxis.deed, and the existing .launcher naming survives intact. No discovery pattern elsewhere in the estate has to learn a new stem shape.

It is also the semantically correct head. A per-app launcher descriptor states what the launcher does for that app — its runtime, its version-output behaviour, its desktop integration. That is a verb, and praxis-deed is the verb form. It composes with what already exists: launcher-standard_praxis.deed states the general rules; <app>.launcher_praxis.deed states that app's conforming praxis. Same head, two scopes.

Arm A — add a fifth head (launcher-deed + its stem pattern)

Honest cost: six sites across three repos, plus an owner ruling — all four existing heads are owner rulings, and praxis-deed was RULED 2026-09-08 on standards#752 as "a genuine fourth head, not a facet of repo-deed."

repo site
standards 1-formats/deed/spec/abnf/deed.abnf:46 (doc-head) and :66 (deed-filename)
standards 1-formats/deed/spec/DEED-GRAMMAR-SPEC.adoc:192 (DocHead) + stem table :276-295 + dispatch side condition :301-308
standards 1-formats/deed/README.adoc (stem/head table)
deed-ecosystem validate-action/validate-a2ml.sh:167 — a regex alternation over the four heads
deed-ecosystem conformance/manifest.a2ml, conformance/run-deed-tests.sh:15, conformance/README.adoc:28
launch-scaffolder crates/launcher-common/src/deed.rs:187 — pub const DOC_HEADS: [&str; 4], a fixed-size array, so a typed change here and a silent one everywhere else

Justified only if a launcher descriptor is genuinely a fifth document form rather than a praxis document. The burden is on arm A to show that.

Arm C — <app>_chora.deed

Semantically wrong, recorded so it is not rediscovered. repo-deed is "What a repo IS — a record", a noun. A launcher descriptor is not a record of what a repo is. It also discards the .launcher infix that arm B keeps for free.


Question 2 — the grammar. A rewrite, not a rename.

The descriptors are TOML-shaped; a deed is not TOML, and the differences are structural:

descriptors use a deed permits
key = value no = as a separator — KEYWORD Sep Value inside s-expressions (= may appear only inside a symbol)
[section] headers no sections — nested ( ... ) clauses; the only bracket is (
tabs tabs are INVALID separators
any UUID uuid5 only (#u5"…"); v4 is deliberately excluded
true / false #t / #f only
TOML escapes exactly four: \" \\ \n \t — \r and \uXXXX are invalid

⚠ The two constraints the first draft of this issue missed

1. :beholding-chora is REQUIRED, and it must be a uuid5. DEED-GRAMMAR-SPEC.adoc:379-414, The praxis-deed form: a praxis deed requires :schema-version, :canonical-name and :beholding-chora. The reason is the one declaration site rule (:398-400, :420-433) — a tool may not declare its own vocabulary, so it must name the chora it reads, "A UUID, never a bare filename — a bare filename resolves against nothing."

So every converted descriptor must name a chora. The good news is that one already serves: launcher-standard_praxis.deed beholds #u5"estate/chora". Whether 21 per-app descriptors should behold the estate chora directly, or a dedicated launcher chora should be declared once and beheld by all of them, is a design decision this issue must make — it is not a detail to settle per-file during a conversion.

2. The praxis field set beyond those three is formally provisional. The spec carries a WARNING: the owner ruled the head but "did not rule on which fields beyond these three a praxis deed must carry — candidates such as :invokes, :emits and :may-refuse are not specified here, because inventing them is precisely the failure this document exists to stop."

Read strictly that blocks arm B, since a launcher descriptor's substance (runtime, version-output, integration) has no specified fields. But there is working precedent in the very file D73-C shipped: launcher-standard_praxis.deed already carries :standard-version, :standard-date and :compliance beyond the required three. So the practical question is not "may a praxis deed carry more fields" — it demonstrably does — but "which fields, named once, for all 21." That is this issue's real substance, and it should be settled as one vocabulary rather than invented 21 times.

⚠ Two versions, not one. :schema-version is the DEED grammar (1.0.0); :standard-version is the document (0.4.0 on the launcher standard today). A converted descriptor carries the first and cites the second. Conflating them makes a stale document read as a newer spec.

⚠ launch-scaffolder has no per-app config deed grammar today. #36 shipped a reader for the standard; there is no schema, reader or writer for a per-app descriptor.


Sequencing

#40 (compat reader, dual-accept)  →  this issue (design)  →  dry-run manifest  →  conversion

Nothing here moves a file. The citation-text fix for these same descriptors is a separate already-ruled workstream (standards#960 AC1): comment-only, independent of the file extension, and must not be folded into this migration.


Acceptance criteria

  1. The filename arm is ruled (A, B or C) with the reason recorded.
  2. The beholding question is ruled: estate chora directly, or a dedicated launcher chora declared once — with the uuid5 named, not left as a filename.
  3. A per-app descriptor field vocabulary is written down once, covering every [section] and key = value in the current descriptors. A TOML key with no deed equivalent is a recorded finding, not a silent drop. Fields are named in one place and reused, never invented per file.
  4. A conformance fixture pair — one valid, one invalid — lands in the deed conformance corpus for the chosen form, and the corpus runner exercises them.
  5. launch-scaffolder reads the new form, with a round-trip test proving a descriptor survives read→write→read byte-identically.
  6. A dry-run manifest lists every file the conversion would touch, current and proposed path, reviewed before any file moves. Both forms are accepted for a stated overlap window before the old one is refused.
  7. If arm A is ruled: all six sites are updated in one change, and the conformance suite fails if any is missed — the [&str; 4] array makes a partial change a compile error in one repo and silent in the others.

Cross-references

🤖 Generated with Claude Code

https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    refactorRestructuring that preserves observable behaviour

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions