Skip to content

Root applies to every surface, and the rename plan says what is stale about it - #717

Merged
David-Noble-at-work merged 2 commits into
developfrom
role-placement-docs
Aug 27, 2026
Merged

David-Noble-at-work merged 2 commits into
developfrom
role-placement-docs

Conversation

@David-Noble-at-work

Copy link
Copy Markdown
Contributor

Documentation only. Two commits, independently reviewable.

1. Three corrections in pkg/op, all surfaced by ui

ui became the first RoleModule|RoleAction|RoleRoot provider in #708, which made three differences
observable that had never been exercised.

RootProviders described a filter that does not exist. Its doc said callers filter the returned slice by
dispatch zone, and named plan.Provider as the example that "filters to RoleAction". It does not —
buildPromotedBuiltins passes registry.RootProviders() through untouched, so every method of every root
provider is promoted into plan.*. The code is correct; the comment described an intent never implemented.

Why nobody noticed: flow is the only other root provider and it is surface=graph, so it holds one dispatch
bit. The distinction between "root everywhere" and "root on the action surface" had no observable consequence
until a provider held both.

RoleRoot now states its intent. One bit, every surface, deliberately. A provider holding both dispatch
bits surfaces flat in both namespaces from a single +devlore:root=true — note() in a script and
plan.note() in a graph. There is no way to be root in one zone and nested in the other, and nothing a
provider could mean by asking: a name is promoted because it reads better without its qualifier, and that is
as true of a graph as of a script.

The surface-to-role mapping existed only in generate.star, as a switch, with no prose anywhere. The
provider-development guide's directive table listed lifetime and bind and omitted both surface and
root.

+devlore:surface= Roles with +devlore:root=true
absent — the default RoleModule|RoleAction RoleModule|RoleAction|RoleRoot
graph RoleAction RoleAction|RoleRoot
module RoleModule RoleModule|RoleRoot

Now in the guide and in 3.6-method-classification.md, both carrying the root-applies-everywhere rule, so a
reader arriving at either gets it.

2. The rename plan says what is stale about it

docs/plans/workflow-rename.md presented 2026-05-26 measurements as current and claimed a prerequisite
satisfied months ago. Every finding lived in issues the plan did not reference, so a reader arriving there got
stale facts with no sign a re-audit was chartered.

Element Audited 2026-05-26 Measured 2026-08-27
pkg/op/ root .go files 62 108 +74%
provider/ files 76 238 +213%
Qualified op.X references ~2,499 3,880 +55%

The subpackage list is wrong in both directions — sops/ has left, claimcheck/ and server/ arrived.

What still holds: all five renames are well-formed, and the collision claim survives (Step declared only
in internal/console).

New evidence for the target: plan.Provider now exposes AssembleDefinition, LoadDefinition, and
SaveDefinition, all returning *op.Graph, and scripts read plan.assemble_definition(...). The API already
calls a graph a definition while the Go type is still Graph. The rename closes a gap that widened on its
own, rather than imposing a new word.

Appendix: Name Mapping — TO BE WRITTEN. The Rename Mapping section is five rows of top-level types;
nothing maps the derived identifiers. 53 exported identifiers contain Graph, 37 contain Subgraph, 18
contain RecoveryStack, and 14 files carry a renamed word in their name. Goal 4 promises reviewers can verify
by inspection — without this appendix there is nothing to inspect against.

The section works through why it needs judgment rather than derivation. Node is ambiguous by construction:
it maps to Step and is the target of ExecutableUnit. GenerateNodeID is the proof —
flow/helpers.go calls it as GenerateNodeID(string(Subgraph)), so it mints ids for subgraphs and names the
abstract vertex, which under the new taxonomy keeps the name Node. A mechanical Node → Step sweep
would rename it GenerateStepID and quietly make it wrong.

Status stays draft. Leaving draft is #716's last acceptance item and should not happen while the numbers are
annotated rather than re-measured.

Tracked

Verified

make check, make test-race, and scripts/Test-GuideFrontmatter.sh all pass.

David Noble added 2 commits August 27, 2026 14:04
…ing is written down

Three corrections, all surfaced by ui becoming the first RoleModule|RoleAction|RoleRoot
provider (#708) and making differences observable that had never been exercised.

RootProviders described a filter that does not exist
  Its doc said callers filter the returned slice by dispatch zone and named plan.Provider
  as the example that "filters to RoleAction". plan.Provider does not filter --
  buildPromotedBuiltins passes registry.RootProviders() through untouched, so every method
  of every root provider is promoted into plan.*. The code is correct; the comment
  described an intent never implemented. Corrected, with a note on why nobody noticed:
  flow is the only other root provider and it is surface=graph, so the distinction had no
  observable consequence until ui arrived holding both dispatch bits.

RoleRoot now states its intent so it cannot be lost
  One bit, every surface, deliberately. Placement is orthogonal to dispatch, so a provider
  holding both dispatch bits surfaces flat in both namespaces from a single
  +devlore:root=true: note() in a script AND plan.note() in a graph. There is no way to be
  root in one zone and nested in the other, and nothing a provider could mean by asking --
  a name is promoted because it reads better without its qualifier, and that is as true of
  a graph as of a script.

The surface-to-role mapping existed only in generate.star
  As a switch, with no prose anywhere. The provider-development guide's directive table
  listed lifetime and bind and omitted BOTH surface and root. Now in the guide and in
  3.6-method-classification.md:

    absent (default)  RoleModule|RoleAction    + root -> |RoleRoot
    graph             RoleAction               + root -> |RoleRoot
    module            RoleModule               + root -> |RoleRoot

  Both documents also carry the root-applies-everywhere rule, so a reader arriving at
  either one gets it.

Related: #715 renames these roles, because surface=graph produces RoleAction -- two
vocabularies for one idea -- and the op->workflow rename retires both words.
… lacks

The plan presented 2026-05-26 measurements as current and claimed a prerequisite that has
been satisfied for months. A reader arriving at it got stale facts with no sign that a
re-audit was chartered -- every finding lived in issues the plan did not reference.

  A banner up front: prerequisite satisfied, numbers stale, do not start a phase from
  them, with pointers to #716 and #715.

  Prerequisites corrected in place, with the old blocker struck through rather than
  deleted. It was true when written, and the record should show that.

  Staleness (measured 2026-08-27), beside the Current State it corrects: root .go files
  62 -> 108, provider files 76 -> 238, qualified op.X references ~2,499 -> 3,880. The
  subpackage list is wrong in both directions -- sops/ has left, claimcheck/ and server/
  arrived. What still holds: all five renames are well-formed and the collision claim
  survives, Step being declared only in internal/console.

  New evidence FOR the target, also recorded: plan.Provider now exposes
  AssembleDefinition, LoadDefinition, and SaveDefinition, all returning *op.Graph, and
  scripts read plan.assemble_definition(...). The API already calls a graph a definition
  while the Go type is still Graph. The rename closes a gap that widened on its own.

  Appendix: Name Mapping -- TO BE WRITTEN. The Rename Mapping section is five rows, the
  top-level types, and nothing maps the derived identifiers -- 53 exported identifiers
  contain Graph, 37 contain Subgraph, 18 contain RecoveryStack, and 14 files carry a
  renamed word in their name. Goal 4 promises reviewers can verify by inspection; without
  this appendix there is nothing to inspect against.

  The appendix section works through why it needs judgment rather than derivation. Node is
  ambiguous by construction: it maps to Step AND is the target of ExecutableUnit, so it
  appears on both sides. GenerateNodeID is the proof -- flow/helpers.go calls it as
  GenerateNodeID(string(Subgraph)), so it mints ids for subgraphs and names the ABSTRACT
  VERTEX, which under the new taxonomy keeps the name Node. A mechanical Node -> Step
  sweep would rename it GenerateStepID and quietly make it wrong.

  And the requirement that identifiers KEEPING their name be listed explicitly: a table of
  only the changes cannot distinguish "correctly unchanged" from "missed".

  Related Rename: the provider roles, pointing at #715.

Status stays draft. Leaving draft is #716's last acceptance item, and should not happen
while the numbers are annotated rather than re-measured.
@David-Noble-at-work David-Noble-at-work self-assigned this Aug 27, 2026
@David-Noble-at-work
David-Noble-at-work merged commit 12cde36 into develop Aug 27, 2026
12 checks passed
@David-Noble-at-work
David-Noble-at-work deleted the role-placement-docs branch August 27, 2026 21:41
David-Noble-at-work added a commit that referenced this pull request Aug 28, 2026
…alue (#726)

Closes #715.

+devlore:surface=graph produced RoleAction and +devlore:surface=module produced
RoleModule -- two vocabularies for one idea, connected only by a switch in generate.star.
An author wrote graph and the generated code said Action. The op->workflow rename forces
the issue: Graph becomes Definition, so surface=graph would name a type that no longer
exists, and module is starlark's word for a namespace rather than a term in the new
taxonomy. Neither word survives.

  +devlore:surface=script|workflow     a list; default both
  +devlore:placement=promoted          one value; default qualified

  type ProviderFlags uint16   packs both
  type Surfaces uint8         BIT FIELD -- SurfaceScript, SurfaceWorkflow
  type Placement uint8        VALUE -- PlacementQualified, PlacementPromoted

The zones are not the same kind of thing, and the design says so three ways
  PLURAL for the set, singular for the value. flags.Surfaces() returning
  SurfaceScript|SurfaceWorkflow reads correctly; flags.Surface() returning two things
  would read as a contradiction. The accessor signatures alone say which zone is which.

  DISTINCT TYPES per zone, following reflect, where flag.kind() returns Kind rather than
  another flag. flags.Placement() == SurfaceScript is now a compile error rather than
  nonsense that compiles. C# disagrees -- MethodAttributes packs a MemberAccessMask zone
  and flag bits in one enum -- and Go's answer was taken for the type safety.

  PlacementQualified = 0x00 is the zero VALUE of an enumeration, not an absent flag, so
  nothing can express "promoted and qualified" -- not because a check rejects it, but
  because the zone holds one value of a type with two.

Why ProviderFlags rather than something specific
  ProviderRole was specific and went stale: #677 changed the zone from invocation mode to
  surface eligibility, and RoleAction, Dispatch(), and ProviderRole all outlived what they
  named. Dispatch() -> Surfaces() for the same reason. A generic name cannot fail that
  way, and the accessors carry the meaning. Precedent: 14 *Flag/*Flags types in Go's
  stdlib, and C#'s BindingFlags.

The bootstrap had no two-pass escape
  generate.star emits the constant names into every .gen.go, so renaming them broke all 58
  generated references at once, the tree stopped compiling, star could not rebuild, and
  codegen -- the only thing that could rewrite those files -- could not run. Unlike #708
  no ordering avoids it: the constant name and its generated use must change together.

  make star-lkg first, so a prebuilt star could regenerate a tree it could not itself
  compile. It took five iterations of generate.star scope errors to get through, and the
  tree was uncompilable during all of them; each would have been unrecoverable without it.
  The LKG was then removed and a source-built star reproduced all 159 files identically,
  proving it was an escape hatch rather than load-bearing.

A property that was documented three times and tested zero times
  #717 established that ui, reaching both surfaces, promotes six names into plan.* as well
  as installing six globals. That claim lived in three doc comments and no test -- and it
  is the half of placement this rename routes through new code.
  TestProvider_ResolveAttr_Tier2_PromotedAcrossBothSurfaces now asserts it, with a
  negative case proving file.copy stays qualified. Without the negative half it would pass
  on a change that promoted everything.

Also: promoteRootMethods -> promoteMethods and rootNames -> promotedNames, resolving an
identifier that carried two words for one idea. Placement test functions renamed;
fsroot, run-root, scoped-root, and git-root names deliberately untouched -- a word-level
sweep over "root" would break their meaning without breaking the build (#725 tracks the
last two artifacts).

The accessors mask explicitly rather than relying on the conversion to truncate, which
gosec G115 caught: the bare form is correct only while both zones are a byte wide.

Co-authored-by: David Noble <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant