tom_specs_reviewer — reviewing the TomSpecs object model

> Cross-references. > tom_specs_model/doc/tom_specs_reviewer_specification.md > is the authority for what this app must be — its purpose > (tom_specs_reviewer_specification.md §1), its inputs > (tom_specs_reviewer_specification.md §2), the review vocabulary > (tom_specs_reviewer_specification.md §6) and the persistence contract > (tom_specs_reviewer_specification.md §7). > tom_specs_model/doc/tom_specs_model_meta_schema.md > owns the snapshot's on-disk schema and its two version stamps. This README is > the user's path through the tool — run it, refresh the snapshot, walk a > document, record a finding, find the file afterwards; those documents own > what the app must be and what the snapshot must contain.

Tree browser and structure-review tool for the TomSpecs object model, used to gather observations for further development of tom_specs_model.

Where this fits

TomSpecs is a method for building software from structured specification documents, and the shape of those documents is fixed by a single Dart source model — the Specification Object Model (SOM), in tom_specs_model. That model is over a thousand classes across fourteen document roots, and it is developed by reading it, one subtree at a time. This app is how that reading is done and how it produces something durable.

It exists because reading a thousand-class model in an editor produces judgements that evaporate. Without it, "this subtree should not be routed to CodeSpecs at all" is a sentence in a chat log; with it, that judgement is a record keyed to the exact structural position that provoked it, in a YAML file that maps cleanly back onto the model. The findings then drive the next round of model work.

So it sits beside the TomSpecs pipeline rather than in it: it consumes the class graph that tom_specs_clitool exports from the model, and nothing consumes the reviewer in turn — its output is read by people changing tom_specs_model. It is explicitly not a specification editor: the app that authors actual DocSpecs / CodeSpecs / Implementation specifications is tom_forge/tom_specs_editor. And it is outside the release-1 published set — it is a publish_to: 'none' workspace tool, excluded by name in tom_specs_clitool/tool/release_set.yaml, so nothing in the released chain may depend on it.

Overview

The app loads two files at startup and mounts a tree over the first, keyed by the second:

1. assets/spec_model.json — the exported class graph, a committed snapshot rather than a build artifact. Startup is synchronous and total: the snapshot is decoded and the review file loaded before the first frame, so no screen ever renders against a partially loaded model. 2. the review file — structure_review.yaml, holding one entry per structural path that carries a judgement.

A structural path is the chain of member names from a document root, with all elements of a list sharing one §item segment. Keying on structure rather than on a rendered instance is what makes a decision about a list element a decision about the element type — it survives the list growing or shrinking.

The third property that shapes everything is negative: nothing the model states may be invisible. A reviewer cannot flag what the tree does not draw, so every annotation the export emits is rendered, and that coverage is held by a test rather than by discipline.

Features

Documents

The model has 14 document roots (@Document), listed in the left rail:

IdDocument
SBPSolution Blueprint
CLACurrent Landscape Assessment
TOMTarget Operating Model
IFMInformation Model
RSPRequirements Specification
ISCInteraction Scenarios
ATSArchitecture & Technology Specification
IISIntegration & Interface Specification
SASSecurity & Access Specification
XDSExperience Design Specification
QAPQuality & Acceptance Plan
DRMDelivery Roadmap
TRPTransition & Rollout Plan
CGPCodeSpecs Generation Projection

CGP is a projection, not a fourteenth authored document — it is marked as such in the rail. It re-reaches the @CodeSpecKind-tagged subtrees that already live under the other roots, regrouped by the shared / client / server locus they generate into. So a class seen under CGP is the same class seen elsewhere, and a review recorded against it is recorded against that one class. Because its content is borrowed, the model validator exempts it from the per-document detail-count check.

The toolbar carries three view toggles, held above the tree so they survive a document switch: cut at detail hand-offs, cut at maps hand-offs, and show serialization order. The two cuts stop the tree where a section hands its detail to another document, which is what makes a single root readable on its own.

What the tree renders

Every annotation the exported model emits is accounted for — a kRenderedAnnotations set names them and a test diffs it against the shipped asset, so an annotation the model starts emitting fails the suite rather than passing unseen.

GroupAnnotations and how they draw
Identity and headline @Document (the root rail), @SectionId and @SectionIdPattern (badges on the row), @Headline (quoted secondary label)
Shape @Form (the form panel under the row), @ContentType and @Min (the row's type label), @ContentHelp (the row's doc line)
Hand-offs and taxonomies @MapsTo and @DetailedIn ( maps→ / detail→ chips, and the anchors the two cut toggles act on); the three routing verdicts @CodeSpecKind , @FollowUpKind and @NoArtifact (part / process / na:<reason> chips); @CodeSpecsProjection (the projection badge)
Closed choices @OneOf (the choice group node) and @Case ( case: chips on its alternatives)
Markers, notes and provenance @Unused (struck-through label plus an unused chip), @Comment (inline ← note), @Reference and @StandardReferences (behind a refs chip — thousands of fields carry standards, and inlining them would bury the structure), @SerializationOrder (a #n badge, behind the toolbar toggle)

What each marker says is not this app's own decision: the chip labels, tooltips and suppression rules live in tom_som_dart_runtime, shared with tom_forge/tom_specs_editor, which renders the same class graph in the Forge shell. The two apps paint them differently — opposite backgrounds — but may not disagree about their meaning. A showcase fixture in that package exercises every annotation, and both apps run a widget test asserting each marker reaches their screen, so a rendering dropped from one tree fails a test instead of drifting apart quietly (tom_specs_reviewer_specification.md §3.1).

What you can record

Per node:

GroupJudgements
Destination Where the subtree belongs: CodeSpecs, follow-up, both or neither. One choice rather than competing booleans, with an explicit undecided state so "no judgement yet" is distinct from "neither"
Scope and progress Scope, stop here / add details markers, a reviewed checkmark, plus a free-text comment. Always visible
Structure List-vs-single, content-vs-form, and the closed-choice judgements (these siblings are really alternatives; the closed set is missing a case)
Annotations The section id / pattern is wrong or collides; the @MapsTo / @DetailedIn hand-off points at the wrong target; the @ContentType is wrong; standard references are wrong or missing; and the keep-or-drop verdict on an @Unused marking (one verdict, so confirming and rejecting are mutually exclusive)
CodeSpecs mapping Whether the node should carry a @CodeSpecKind and does not, whether the kinds it declares are wrong or incomplete, which CodeSpecPart kinds are proposed instead, and whether the node should not be realised as code at all. Proposed kinds are validated against the CodeSpecPart vocabulary at entry, so a recorded suggestion always maps back onto the model
Follow-up mapping Whether the node should carry a @FollowUpKind and does not, whether the declared processes are wrong or incomplete, and which FollowUpProcess codes are proposed instead. That taxonomy is explicitly extensible, so a code outside it is warned about, not rejected — proposing a process nobody has named yet is a legitimate finding
No-artifact verdict The third routing verdict, with its own group rather than a corner of the CodeSpecs one: whether the node feeds nothing and should carry @NoArtifact but is routed anyway, whether it carries @NoArtifact but does feed something downstream, whether the declared reason is the wrong one of the three, and which NoArtifactReason is proposed instead. That last is a single choice, not a set — a section becomes several parts or feeds several processes, but it is unrouted for one reason

The axis-specific groups are collapsible. A group that already holds a judgement opens expanded, so recorded feedback is never hidden behind a collapsed header. The full vocabulary, field by field, is set out in tom_specs_reviewer_specification.md §6.

Quick start

The app is not published; it is run from this checkout. Its only run prerequisite is a current assets/spec_model.json — the snapshot is committed, so a fresh clone runs immediately, and refreshing it is a separate step (see Usage below).

flutter pub get
flutter analyze
flutter test
flutter run        # or: flutter build bundle | web | linux | macos

flutter run opens on the start page: the model stamp bar across the top, the 14 document roots in the left rail, and the tree for whichever root is selected.

Usage

Refreshing the model snapshot

assets/spec_model.json is a committed snapshot, not a build artifact. The stamp bar shows the export time, the class and root counts and the container root; it turns into a warning when the snapshot is older than two weeks or when its declared counts disagree with its own payload. Both conditions mean the same thing to a reviewer — you may be recording judgements against a model that has moved — and the cure is one command, run from tom_specs_clitool:

cd ../tom_specs_clitool
dart run bin/model_json.dart --target reviewer

Naming the target is what keeps the stamp right: the target owns the output path, and the stamp is derived from the model's own version.versioner.dart, so neither can be supplied wrongly. Refreshing this snapshot is a re-export of the current model, never a renumbering of it — which the derived stamp expresses exactly, since modelVersion stays put while modelVersionLabel records the build the snapshot was taken from. The editor's copy of the same export carries the same stamp, because the two assets are one export of one model. The full procedure for both targets lives in one place: tom_specs_model_meta_schema.md, "Refreshing the committed assets".

Expected stamp after a clean refresh:

KeyValue
modelVersion / modelVersionLabel 1 / 1.3.2+11.f4804777
metaSchemaVersion1
classCount1254
rootCount14
containerRootDocSpecsProject

modelVersion is the major of modelVersionLabel — the two are one fact in two forms, so a snapshot whose counter does not match its own label's major describes no model that ever existed.

Only generatedAt and the build component of modelVersionLabel should differ when the model itself has not moved. A change in classCount or rootCount means the model has moved — expected after model work, and worth a glance at the diff before committing. Note that a doc-comment edit in tom_specs_model also changes the export, because doc comments travel with the model as @ContentHelp text.

This table is a maintained baseline, not decoration: four tests in test/widget_test.dart pin it to the shipped asset, so a refresh against a differently sized model fails the suite until the table is updated with it.

Walking a document and recording a finding

1. Pick a root in the left rail. Leave the two cut toggles on — they stop the tree at @MapsTo / @DetailedIn hand-offs, which is what makes one root readable without the other thirteen. 2. Expand to the node the judgement is about. Chips on the row carry what the model already declares: its routing verdicts, its hand-offs, its closed choices. 3. Open the node's review controls and record the judgement. Destination, scope and comment are always visible; the axis-specific groups (structure, annotations, CodeSpecs mapping, follow-up mapping, no-artifact) collapse until they hold something. 4. The finding is written through immediately — there is no explicit save.

The review file

Findings are written to structure_review.yaml, keyed by structural path. The file holds the review payload only. It is a clean reverse mapping onto the object model, so view state (toggles, expansion) is deliberately kept out of it — the file never carries anything that is not a statement about the model.

Its location can be overridden with the TOM_SPECS_REVIEW_FILE environment variable; the default is <cwd>/review/structure_review.yaml:

TOM_SPECS_REVIEW_FILE=/path/to/review/my_pass.yaml flutter run

Architecture

Five source files, split by responsibility, over the two shared layers in tom_som_dart_runtime:

                    assets/spec_model.json          structure_review.yaml
                             │                                │
                             ▼                                ▼
                        lib/main.dart  ──── loads both before the first frame
                             │
                             ▼
                    lib/src/ui/start_page.dart
                    root rail · stamp bar · view toggles
                             │
                             ▼
                    lib/src/ui/spec_tree.dart ───► lib/src/ui/review_controls.dart
                    the tree and all rendering     the per-node review dialog
                             │                                │
                             └────────────┬───────────────────┘
                                          ▼
                            lib/src/model/review_store.dart
                                          │
                                          ▼
              tom_som_dart_runtime : readers + display semantics (shared)
TypeResponsibility
SpecsReviewerApp The MaterialApp root; mounts the start page over the loaded snapshot and store
StartPage Root rail, model stamp bar, view toggles — the state held above the tree so it survives a document switch
ModelStampBar Renders the snapshot stamp and raises the staleness warning
SpecTree / SpecTreeScope The structure tree and every annotation rendering; the scope carries the toggles and the store down the tree
ReviewControls The per-node review dialog and the one-line summary shown on the row
ReviewEntry One node's recorded judgement — the full review vocabulary as fields
ReviewStore The keyed collection of entries, its YAML persistence, and change notification
ReviewDestination / ReviewScope The two closed choices with an explicit undecided state

The readers (SpecModel, SpecRoot, SpecClass, SpecField, FormFieldSpec, SpecFieldKind) and the display semantics (SpecChip, SpecRowExtras, kRenderedAnnotations) are not app-local — they live in tom_som_dart_runtime and are shared with the editor. Only the paint is here.

Ecosystem

            tom_specs_model              ← the SOM source model
                    │
                    │  exported by tom_specs_clitool/bin/model_json.dart
                    ▼
          assets/spec_model.json  (committed snapshot, one export, two targets)
                    │
        ┌───────────┴────────────┐
        ▼                        ▼
  tom_specs_reviewer      tom_forge/tom_specs_editor
  ← this package             the authoring app
        │                        │
        └──── both read ─────────┘
                    ▼
          tom_som_dart_runtime + tom_specs_core
          readers · display semantics · CodeSpecPart vocabulary

Nothing depends on tom_specs_reviewer. It is a leaf, deliberately outside the release-1 set, and its output is a YAML file read by people changing tom_specs_model.

It does not depend on tom_som_dart_v0, the typed facade, and that is a decision rather than an omission: the facade earns its keep by making document edits correctness-checked by the generated model, and this app loads the class graph, never a document (tom_specs_reviewer_specification.md §2.4).

Further documentation

TomSpecs subject matter — the authorities this package serves:

DocumentAuthority for
index.md The catalogue of the whole TomSpecs document set, and the § citation convention used throughout it
tom_specs_reviewer_specification.md What this app must be — purpose, inputs, the start page, the tree, the review vocabulary, persistence
tom_specs_model_meta_schema.md The snapshot's on-disk schema, its two version stamps, and the refresh procedure for both committed assets
tom_specs_model_rules.md What each annotation the tree draws means, and the structural invariants a finding may be about
codespecs_mapping.md The CodeSpecPart catalogue and the three routing verdicts @CodeSpecKind / @FollowUpKind / @NoArtifact that this app records judgements against
tom_specs_editor_specification.md The other app — what a specification editor is, and why this one is not it

This package — its own guide:

GuideCovers
doc/review_workflow.md The reviewer's path through the tool: refresh the snapshot, open a root, walk the tree, record a finding, and find the review file afterwards

Siblings — packages you will reach for next:

PackageWhat it is
tom_specs_model The SOM source model this app reviews
tom_specs_clitool The exporter that produces assets/spec_model.json
tom_som_dart_runtime The shared readers and annotation display semantics
tom_specs_core The annotation vocabulary, including the CodeSpecPart enum a proposed mapping is validated against

Status

Version 1.0.0+1, publish_to: 'none' — a workspace tool, outside the release-1 published set and excluded by name in tom_specs_clitool/tool/release_set.yaml.

Functional. Two test files — test/widget_test.dart and test/structure_annotation_rendering_test.dart — carrying 126 tests, all green. Several of them render the shipped asset rather than a fixture, deriving every expectation from it, because the risk they guard is precisely that the renderer handles a hand-made shape and not the real one.