One convention for preliminary or to-be-amended information — brackets, everywhere, rather than silent omission #162
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
m, 2026-07-30, asked whether a generated draft title should show a missing opponent the way the filename already shows a missing docket:
So the specific case is low priority. The general one is the issue.
The problem
Today each surface handles an unfilled value its own way, and one of them is silent:
(TBD)— visibleFour mechanisms, four spellings, and only the draft title makes an incomplete thing look finished.
What m is asking for
One convention for "this value is preliminary or needs amending", applied consistently wherever generated text can carry an unfilled or provisional value. His suggestion is bracket notation —
[ … ]or similar.Scope to establish before designing
unsourcedfromnot_applicablefor exactly this reason, and that distinction turned out to matter. This may need the same split.mAi/patentstyle.Explicitly out of scope
The draft-title case that prompted it. m ruled it a rare edge case; fix it when the convention exists, not before.
Filed by paliad/head from m's answer of 2026-07-30. Related: the filename generator's
(TBD)handling is the one surface that already does something reasonable and is worth reading first.Scope established + design filed
docs/design-preliminary-value-convention-2026-07-30.mdCommit: https://mgit.msbls.de/m/paliad/commit/6d7c1fe (branch
mai/ares/issue-162-one-convention)The issue lists four surfaces. There are nine, and the two it does not list are the two that decide the design.
The two missing surfaces
Brackets are already the house convention, and they already reach filed documents. Three surfaces use
[ … ]in the body of a Word file today:[KEIN WERT: project.case_number]— the.docxsubstitution marker (pkg/docforge/placeholder.go:26)[Hier folgt der Schriftsatztext …]— the fallback skeleton (pkg/docforge/docx/fallback_skeleton.go:212)[title / identifier],[Insert original text here]— patentstyle's own.dotmelements, vendored atfrontend/scripts/patentsstyle-wiki/src/05-special-elements.de.md:87,96,100So m's suggested notation is not a new proposal. It is the convention the firm already uses; three paliad surfaces follow it and four do not.
One surface already models "preliminary" as data, not text. The Fristenrechner's assumed date: a user-declared hypothetical taints every deadline computed from it (
pkg/litigationplanner/engine.go:223), the taint propagates transitively, the UI renders<date> (angenommen)plus a premise line, and the promote path refuses to carry assumed deadlines into the real Fristenliste. That is the reference implementation.The three scope questions, answered from measurement
Where does provisional information occur? Six values: docket, party, deadline date, court, evidence identifier, submission body. All one class — a value the firm will know later. And no data concept exists behind any of them:
models.Partyhas no served/provisional flag,project.case_numberis a nullable string with no status column. So this is a rendering convention, not a schema change. Building a per-field provisional flag across projects, parties, deadlines and courts to serve one cosmetic difference would be the wrong trade.Is "preliminary" the same as "missing"? No — and paliad has independently answered this twice, in two vocabularies. Mig 211 splits
not_applicable(CLOSED) fromunsourced(permanent until the world changes). The Fristenrechner splitsisConditional("no date could be computed at all") fromisAssumed("the arithmetic is real; the PREMISE is hypothetical"). Both are the same question: will anyone ever look again?That gives a closed set of four states, three of which already have a name in the tree:
pendingassumedisAssumed/angenommenunsourcedRefusalUnsourcednot_applicableRefusalNotApplicableDoes it ever reach a filing? Yes — measured.
ExportWithMarkerdefaultsmissingtoDefaultMissingMarker, so every export path that passesnilproduces a.docxcarrying[KEIN WERT: project.case_number]in the body. No export path counts the remaining markers, warns, or refuses. The download succeeds and the file looks finished.Three things are wrong with that text as filed text: it names a code path (
project.case_number) rather than the thing the lawyer must supply; it asserts absence ("KEIN WERT") where the true statement is not yet; and its wording was inventor default D4 in the 2026-05-19 submission-generator design — a reasonable engineering default, never a house-style decision, never seen by a lawyer.The convention proposed
Square brackets everywhere a human reads the output, never silence. Four rules:
[Aktenzeichen — noch nicht vergeben], not[KEIN WERT: project.case_number]. The human label already exists for every variable innomen.VarDef.Label/LabelEN; the marker just does not use it.not_applicablerenders nothing, which is why the four states cannot collapse into "show a bracket".(TBD)—[and]are glob metacharacters. This is not a special case:nomen.RenderTargetexists precisely so one composition renders differently per output policy.No new mechanism is needed. Both seams already exist and are already per-consumer:
nomen.MissingKind(whoseKindLiteralcomment says it is kept distinct "so future policy can diverge" — this is that divergence) anddocforge.MissingPlaceholderFn.One guard belongs with this work
No export counts its remaining markers. Once markers are the deliberate convention rather than a fallback, an export carrying one should say so — in the shape of
UnresolvedSelections, which already made exactly this trade: do not fail the build, but stop being unaware. Without it the convention makes gaps prettier without making them harder to miss, and a nicer-looking gap in a filed document is worse than an ugly one.Open — needs m (§8 of the design)
The marker text goes into a document that can be served on a court. That is house style, and it touches
mAi/patentstyle, whose.dotmbrackets slots bare ([title / identifier]) with no state suffix.[Aktenzeichen]. One convention across both tools; loses m's own preliminary-vs-needs-amending distinction.[Aktenzeichen — folgt]vs[Gegner — nicht erfasst]. Carries the distinction; two bracket dialects unless patentstyle adopts it too.Recommendation: (B), and take it to patentstyle as a shared convention. A bare
[Aktenzeichen]cannot tell a reviewing partner whether the docket is coming or was forgotten, and those warrant different actions.Not proposed
No schema change. No change to the assumed-date, block-refusal or formalities-refusal surfaces — this work adopts their vocabulary rather than replacing it. No change to the preview "Beispiel" mode. No renaming of
unsourced/not_applicable(those names are patentstyle's, carried verbatim by mig 211).The draft-title case m ruled out of scope is, once the vocabulary lands, the one change that needs no decision from anyone and touches nothing filed — the natural first slice, exactly as m ruled.
No status label set: this repo carries only
deferredanddone, and neither is honest here. Nothing shipped — the convention is proposed and awaits the §8 decision.m's ruling built: bare brackets, draft title shipped
Commit: https://mgit.msbls.de/m/paliad/commit/eb58bc5 (branch
mai/ares/issue-162-one-convention)Decision (m, 2026-07-30):
[Aktenzeichen],[Gegner]— the variable's human label in square brackets, no state suffix. This is patentstyle's existing.dotmform, so one convention holds across both repositories from this commit and the cross-repository proposal is out of scope — there is nothing to propose.What shipped
The draft title, first, as it needed no decision and touches nothing filed.
A project-bound draft now names all three of client, forum and opponent. An unresolved one renders as its bracketed label instead of vanishing:
2026-05-31 Bayer AG ./. UPC2026-05-31 Bayer AG ./. UPC ./. [Gegner]2026-05-31 Bayer AG ./. Acme Generics2026-05-31 Bayer AG ./. [Forum] ./. Acme Generics2026-05-312026-05-31 [Mandant] ./. [Forum] ./. [Gegner]2026-05-312026-05-31 [Client] ./. [Forum] ./. [Opponent]A project-less draft is unchanged. Its trio is inapplicable, not unfilled, and bracketing it would invent three questions nobody asked — the one place where silence is still correct.
Three implementation notes worth knowing:
projectBoundis an explicit parameter, not aproject != nilcheck. A nil pointer on the project path means a project row that failed to load, and that draft's opponent is still a gap to mark. Only the caller knows which naming path it is on.Az. folgtleak the filename placeholder already suffered.overlayMissingRulessplits a composition by owner. The stored override supplies the shape (variables, order, separators, wraps — exactly whatComposition.Template()round-trips); the system supplies the missing-value rules, re-derived every render. That is what lets the wording change later without migrating a single stored override, and it closes the same latent freeze on the filename artifact (no output change there today).Go suite green (
./internal/... ./pkg/... ./cmd/...), 411 frontend tests green, gofmt clean, branch not behind main.The cost, recorded as a limit (§5.1 of the design)
A bare bracket cannot distinguish "the docket is coming" from "nobody recorded the opponent".
[Aktenzeichen]on a draft awaiting registry assignment and[Gegner]on a proceeding whose opposing party was never entered are the same mark, and they warrant different actions — the first is waiting, the second is work.Weighed and declined: one convention shared with patentstyle from day one was judged worth more. Recorded in the design so the next person to feel the gap finds the question already asked, rather than re-deriving it. If it is ever reopened, the thing to change is R3 alone — the render path takes its missing-value rules from code on every render, so no stored composition needs migrating.
TestAutoSubmissionTitleMarkerFormpins the bare form against the shapes it must not drift into (the[KEIN WERT: key]form, a variable key, a colon, a state suffix, the filename's parentheses), so the declined distinction cannot creep back one well-meaning change at a time.Does the distinction belong in the DATA instead? (§8) — measured answer: no
The formalities family kept
unsourcedseparate fromnot_applicablein the data while both go downstream together, and that split mattered. The shape does not transfer here, and the reason is provenance rather than display.A refusal record is the record of a human judgment. Someone read the RoP, concluded R. 52 is the wrong answer for Rejoinder, and wrote a prose reason — mig 211 is explicit that the reason "carries the argument" and exists to stop the next person fixing the refusal onto a filing. It is worth storing because it is not derivable.
There is no equivalent judgment for a docket that has not been issued. Nobody looks at an empty
project.case_numberand concludes anything; it is empty because the registry has not acted. Storingpendingvsunsourcedwould need someone to declare it per empty field — and they will not, because filling the value in is strictly less work than annotating why it is empty. A state field nobody has an incentive to write sits at its default forever, and a default-valued field that looks authoritative is worse than no field.The states also add nothing a query cannot answer today: "which drafts have no docket" is
case_number IS NULL.Rule carried forward: store a state only where a human declared it. Never derive one, and never provide a field hoping someone will.
Two places where it is already in the data, for free — named in §8.1 because they are where the distinction would live if ever wanted, and each already holds more than "empty":
submission_drafts.variables[key] = ""means the lawyer deliberately left this gap visible; an absent key means nobody touched it. That is an authored judgment, it is persisted, and today both render the same marker — the formalities shape exactly, arrived at without anyone designing it.selected_court_idorselected_lawyersid matching nothing records that somebody chose a referent which has since vanished. That isunsourcedin the strict sense, already reported byUnresolvedSelections, already distinguishable from "never selected".Neither is proposed for change.
Next
The two surfaces that touch filed text: the
.docxmarker ([KEIN WERT: project.case_number]→ the catalog label) and the letterhead/party selection (render a marker where a selection dropped, not only report it). Plus the export guard — no export path counts its remaining markers today, and a nicer-looking gap in a filed document is worse than an ugly one.The filename stays
(TBD)deliberately: parentheses are settled by R4, but theTBDtoken is m's own prior ruling (t-paliad-composer-docket-tbd-label) and changing it needs its own decision.Still no status label: this repo carries only
deferredanddone, and the issue is partly shipped with two surfaces open.Slice 2: catalogue-label markers, letterhead gaps, export guard
Commit: https://mgit.msbls.de/m/paliad/commit/dc9bad4 (branch
mai/ares/issue-162-one-convention, merged main first)All three remaining items. Two of them reach filed text.
1. The
.docxmarker[KEIN WERT: project.case_number]named a variable key — a dotted identifier in a Go map — inside a document a lawyer may hand to a court. It now renders the variable's human label in bare brackets:[Aktenzeichen (Gericht)],[Court case number].The label source already existed and no marker used it.
SubmissionVariableCatalogue()is the resolvers' own bilingualVariableKeycatalogue — the same one paliad publishes to the sidebar and the authoring palette — so the marker and the form that fills it now name the same thing.Indexed forms the resolvers deliberately cannot enumerate (
lawyer.2.name,parties.claimant.1.name) normalise onto their alias entry: the second letterhead name is still a letterhead name.TestCatalogMissingMarkerCoversEveryCataloguedKeyfails if any catalogued variable lacks a DE or EN label, which is what keeps the key-path fallback unreachable for anything paliad actually emits.docforge.DefaultMissingMarkeralso changed — it now brackets the bare key rather than spelling[KEIN WERT: key]. The engine holds no catalogue so the key is genuinely all it has, but a second spelling is exactly what #162 exists to remove. Paliad never hits it:services.DefaultMissingMarkerdelegates to the catalogue marker, so all seven render paths get the label form without any call site remembering to ask.2. Letterhead — one of the three sub-cases can be bracketed, and only one
I read
submission_vars_letterhead.goand theUnresolvedSelectionspath first as you said. The surface reports three kinds of unresolved selection and they are not alike. This is the part worth reading, because "render the other two in brackets too" looks like the obvious next commit and is not implementable.Lawyers — bracketed. A selected colleague whose user row is gone now keeps their position as a nil hole instead of being dropped. Compacting the slice renumbered every later colleague:
{{lawyer.1.name}}became whoever was chosen third, and no downstream report can undo a name that landed under the wrong index. Their indexed keys stay unbound, so the ordinary missing-marker path fills them — no second mechanism. Only the comma-joined{{lawyers}}spells the marker out, because a name missing from the middle of a joined string cannot be an unbound key. Both produce[Briefkopf: Name]: one spelling, two paths.When the first choice is the gone one, the unindexed
{{lawyer.name}}alias stays unbound rather than promoting the second choice into the primary sender slot — a real name in a position nobody assigned it is worse than a marker.Parties — no honest bracket exists. An unresolved party id matches no row on the project, so paliad holds a uuid and nothing else: no name, no role, and therefore no position in the caption. Claimants and defendants are separate groups, and a marker cannot be placed in a group when the group is precisely what is unknown.
Court — a substitution, not an omission. An unresolvable
selected_court_idfalls back to the project's court, so no key is unbound and the document names a real court. The defect is that it is not the one the lawyer chose — worse than a gap for that reason. A bracket is the wrong instrument: there is a value, and it is wrong. Replacing the fallback with a marker is a live option but changes filed text in a case where the project's court is often correct, so it is its own decision rather than a consequence of this one. Flagging rather than deciding it.Both uncovered cases land in the audit row instead.
3. The export guard
ReportingMissingMarkerwraps whatever marker a render path passes and records every key it is asked for.ExportWithMarkerattaches the result toSubmissionVarsResult.MissingKeys, and the export handler writes the count, the keys, and anyUnresolvedSelections.Describe()onto thesubmission.exportedaudit row.The wrapper is the whole mechanism deliberately:
MissingPlaceholderFnis already the seam every render path threads, so counting needs no change to the substitution engine and cannot miss a path the engine gains later.The export still succeeds. Refusing a whole submission over one gap is worse than the gap — the trade
UnresolvedSelectionsalready made and justified. The guard makes gaps findable after the fact, not impossible. The remaining half is the editor warning before the download, which is a frontend slice and is not built here.MissingKeysandUnresolvedare separate fields on purpose and both are needed:Unresolvedis a selection that pointed at something gone,MissingKeysis a slot the template asked for and the bag could not fill. Either occurs without the other — the court case is unresolved with no missing key; a template asking for{{project.patent_number}}on a project that has none is a missing key with nothing unresolved behind it.patentstyle
No artifact of theirs changed and none needed to. The markers are produced by paliad's placeholder substitution over the template the
.dotmsupplies; no new placeholder had to appear inside a docPart. Nothing was vendored and no vendored file was touched (git diff --stat -- frontend/scripts/is empty). Had a docPart change been needed it would have come to you rather than being hand-edited.Verification
Go suite green across
./internal/... ./pkg/... ./cmd/..., 411 frontend tests green, gofmt clean, branch merged with main (7c16dbb) and re-tested after.What is left on #162
TBDtoken, which is your separate prior ruling and untouched.The three-way split, filed; and where #162 now stands
Commit: https://mgit.msbls.de/m/paliad/commit/17a5364 (branch
mai/ares/issue-162-one-convention, merged main14faecdfirst)The split, as the axis rather than three observations (design §6.2)
Two questions decide the treatment, asked in order:
Flattening them would produce a marker that lies in two of three cases: a party marker has to guess a role it does not have, and a court marker describes a wrong value as a missing one.
The court — #139 already answered it, on the input side
I read diesel's Slice B before proposing anything, and it is already there:
submission-draft.tsrendersUnbekanntes Gericht: <id>, andunresolvedTextalready states the render consequence in words before generating: "das gewählte Gericht steht nicht mehr im Verzeichnis — der Schriftsatz geht an das Gericht des Projekts".So the substitution is disclosed twice before the document is generated, and since the export guard a third time on the audit row afterwards. The residue is narrow: what still says nothing is the document itself, which names the project's court in ordinary type. A reader holding only the .docx cannot tell an override was overridden, and an export made without opening the editor gets none of the three disclosures.
Three options for m, recorded in §6.5:
I have not chosen. Given that #139 already fixed the disclosure, (a) is defensible and the narrow question is only whether the document should carry what the editor already says.
The parties case — a data question (§6.4)
A rendering question would be answerable with a marker. This one is not, and the comparison shows why:
selected_lawyersselected_partiesroleThe lawyer case is markable only because the order is the lawyer's own choice and outlives the user row. A party id leaves no residue at all — strip the row and it does not even say which side the party was on.
So the question is whether a party selection should record enough to survive the row it points at: at minimum the role, arguably a name snapshot at selection time. That is m/paliad#161's shape — a row missing a field, where the gap blocks several things rather than one cosmetic case — not #162's. Not built, not proposed.
A correction this task owed
My own previous commit falsified one of diesel's warning clauses. It told the lawyer that a colleague with no account "will not appear in the letterhead"; since
dc9bad4they do appear, as a marked gap in their chosen position. A lawyer who reads that and exports anyway expects a shorter letterhead, not a bracket inside one.The test covering that clause asserted the count and the surface name but not the consequence, so it stayed green straight through the change. It now pins the consequence, and pins that the party and court clauses still describe absence and substitution respectively.
Where #162 stands
Nothing remains that does not need a decision from m.
Shipped and merged: the draft title (
eb58bc5/7c16dbb), the catalogue-label.docxmarkers, letterhead position preservation, and the export guard (dc9bad4/14faecd). This commit adds the classification and the warning correction.Open, all three needing m:
TBDtoken — m's own separate prior ruling, deliberately untouched.One thing I had listed as remaining is not open after all: the editor warning before an export. #139 Slice B built it, and it covers all three unresolved kinds. What it cannot cover is
MissingKeys, which only exists after a render — so a post-export signal would be a different surface answering a different question, and the audit row already answers it. I do not think it is worth building.Verification
Go suite green, 413 frontend tests green (2 new), gofmt clean, merged with main and re-tested.
Court ruled, one decision-free slice found and shipped, #162 closes
Commit: https://mgit.msbls.de/m/paliad/commit/98b0039 (branch
mai/ares/issue-162-one-convention, merged main18acbeffirst)Answering the question directly
Neither exactly. m's rulings closed all three decisions and unblocked nothing new — but there was one slice left that needed no decision, and it was drift my own commits created. It is shipped, and now the task closes.
The court ruling (§6.5)
Option (a) leave it, recorded. The residue m accepted is named in the doc so nobody files it as a bug later: an export made without ever opening the editor receives none of the three pre-generation disclosures, and the document names the project's court in ordinary type.
One guard added to the wording: the ruling does not generalise. It says this fallback is usually right, not that a plausible default may stand in silently wherever one is available. The next substitution to appear gets classified on §6.2's two questions on its own merits.
The decision-free slice: my own drift
Two things, same family as the warning corrected last commit.
Fifteen comments across eight files still described the render as producing
[KEIN WERT: key]. Untrue sincedc9bad4. A comment asserting behaviour the code no longer has is worse than no comment, because it reads as current. All rewritten to the form the code actually produces.TestSampleMissingMarkernever tested its own stated property. Its comment said a sample marker must never emit the gap-marker form — that is the whole point of "Beispiel" mode. Its assertion checked that the string was non-empty. So it stayed green through #162 changing the gap marker's form, and it would have stayed green if sample mode had started emitting gap markers outright. It now checks the property — no bracketed output — for both languages and for a known and an unknown key.That is the second instance this week of a test measuring that a message exists rather than that it is true, and both were in code this task touched.
Final state — §9 of the design, so nothing gets reopened by accident
Added as a table in the doc because two of the eight items are closed by ruling rather than by code, and both read as unfinished work otherwise.
[Mandant]/[Forum]/[Gegner]on project-bound drafts.docxmarker[KEIN WERT: key][Briefkopf: Name](TBD)[Label], m's ruling§9.1 states the two that read as unfinished and are not:
One thing found and not fixed (§9.2)
Two label catalogues serve the same job and neither knows about the other:
nomen.VarDef.Label/LabelENfor the name artifacts,SubmissionVariableCatalogue()for the.docxplaceholder bag. Both carry DE/EN labels; until this work neither had a marker consumer. They agree today only because both were wired to the same convention in the same week, and nothing enforces that they stay in agreement. Worth converging before a third appears — separate work, not proposed here.Verification
Go suite green across
./internal/... ./pkg/... ./cmd/..., 413 frontend tests green, gofmt clean, merged with main and re-tested.#162 is complete from this side. No status label set — this repo carries only
deferredanddone, and closing is m's.