Supporting reference · Working design and evidence, not a promise of complete support.

On this page

Candidate Foundation Plan application model

Status: Working hypothesis under test with the eight current Foundation Plan examples.

The recovered base, extended by the current fixtures, is:

Foundation Plan
  └── application
      ├── domain (optional)
      ├── appearance (optional)
      ├── native clients
      ├── delivery channels
      ├── Entity
      │   ├── Account behavior (optional; zero or one Entity per Plan)
      │   ├── icon (optional)
      │   ├── Field
      │   │   └── ordinary Validation
      │   ├── Reference
      │   │   └── ordinary Validation
      │   ├── Association
      │   ├── Predicate
      │   ├── Ordering
      │   ├── entity-level Validation
      │   ├── Tree
      │   ├── Policy
      │   ├── Scaffold
      │   │   └── profile (Account-bearing Entity only)
      │   └── reference data (optional)
      └── development data

The first four keys are a different kind of thing from the Entities below them. They are authored facts about the application as a whole rather than semantic meaning, and they exist because no Entity, Field, or Scaffold can imply a domain, a brand color, or a decision to build an Android client. Decision 0016 (repository-only) records the narrow test that admits them and requires each to carry a revisit trigger.

Each structured subject requests generation. The format may recognize a subject that the selected target profile or Compiler release does not yet support; Analysis lists that subject in the reviewed GapSet and the deployed Compiler compiles the supported subset. Desired outcomes with no bounded structured representation remain with the user's agent rather than entering the Foundation Plan. This is a starting model. The first complete examples and authoring records should be allowed to change it.

This tree describes ownership in the current serialized sketch. An Entity contains the definitions whose subject is that Entity. A Field or Reference contains an ordinary Validation when it is the rule's one target. The rule's condition may still read other values. Supported tuple and cross-value rules stay on the Entity. Account behavior belongs to the Entity that signs in. The standard Scaffold belongs to the Entity whose resource routes it selects. Development data remains application-wide because its records form one connected graph. Readable typed paths such as movie.released_on remain useful for references from elsewhere even when the definition's owner is already clear from nesting.

Nesting removes redundant ownership selectors: an Association does not repeat entity, and a Reference does not repeat from. It does not require First Draft to persist one nested JSON blob. The authoring model can remain normalized while serialization projects its records into an ownership-shaped document for review and compilation.

Entity and Field

An Entity is an application concept with its own identity or collection of instances: User, Post, PostMediaItem, Translation, or Movie.

Every Entity has one primary descriptor. This typed locator names the one value used when a generated interface needs to describe a record as a whole:

{
  "subject_uuid": "019f9425-5412-7e8b-bba7-780ec0465bdf",
  "key": "movie",
  "name": "Movie",
  "primary_descriptor": {
    "field": "movie.title"
  }
}

A descriptor can select one Field, one singular referencing-side Association, or the profile-provided id, created_at, or updated_at system Field. An Association descriptor delegates to the referenced record's descriptor. Dunbar150 can therefore describe a fieldless Like through like.target; a Post or Comment then supplies the final value. The descriptor is a display value, not a lookup key or uniqueness claim.

An authored Field selected as the descriptor is required. An Association descriptor must be an unqualified, referencing-side direct Association over a required Reference. Otherwise a valid persisted record could have no whole-record display value. System descriptors are generated for every persisted record. If optional display data is preferable when present, the initial shape still needs one required descriptor rather than an implicit fallback chain.

For a Field descriptor, requiredness is the only source-eligibility rule in the initial format. Field type, encryption, and log-redaction properties do not disqualify an otherwise valid descriptor. Display and generated-log behavior remain separate concerns.

The descriptor is a generation-time default rather than a model-wide presentation method. The Compiler resolves it into an explicit reader chain at each generated use site. A Bookmark display might therefore contain bookmark.movie.title; it would not depend on bookmark.movie.to_s. After compilation, changing one generated view does not change every other use site that began with the same descriptor.

A one-target forward Association produces one reader chain. An Association over a multi-target Reference may require an explicit branch over its closed target set because each target can choose a different descriptor. The Compiler does not assume that unrelated target models expose a common reader.

The locator belongs to the Entity rather than adding parallel Boolean flags to Fields and Associations. That shape makes the one-descriptor requirement structural and accommodates generated system Fields. Current semantic analysis proves local ownership, resolves every closed Reference target behind an Association, and rejects recursive descriptor cycles. The Rails AnalysisRun now consumes that result from its sealed graph. Its bounded target lowers a direct descriptor or one Association hop ending at a required emitted scalar or system Field. Public index and show render explicit reader chains with the required preload. Public Scaffold Reference selects use the same descriptor for deterministic terminal-value/id ordering and labels. A private Policy-gated index authorizes before preloading that one-hop reader when present. Multi-target branching, longer chains, and broader consumers remain open.

An Entity may also carry an optional icon, a semantic token from a closed vocabulary such as film or person. It sits beside the descriptor for the same reason: both describe how this Entity is depicted wherever it appears, rather than belonging to one generated surface. Entity-index entries in application navigation (repository-only) are its first consumer. The fixed Account self entry instead uses the target-derived semantic person token. A client surface decides whether and how to display either token; the current iOS lowering maps it to an SF Symbol, while a Android lowering chooses the corresponding Material icon. Omission takes a neutral fallback.

A Field is a named semantic value read from an Entity. A Field is not necessarily one database column. A fixed-currency money Field might lower to a subunit column, a Money-valued model attribute, form controls, formatting, and query translation. An uploaded image might lower to attachment records, validation, Cloudinary-optimized delivery, and generated tests.

The old catalog usefully distinguished scalar, value-object, derived, and special Fields. The current sketch uses semantic types for recurring value shapes and a separate derivation property for a numeric value whose current value depends on other declared data. The Field catalog records the current boundary, including required enums and bounded State Machines.

The current serialization treats an enum as a semantic Field type rather than arbitrary text plus an inclusion rule. It uses type: "enum" with settings.values as ordered { "subject_uuid", "key", "name" } objects and an optional settings.ordinal flag. That closed domain can inform generated controls, queries, validation, and integrity while leaving its physical Rails backing to the target profile.

The current 0.23 sketch includes ten Field types whose meaning goes beyond a simple scalar column:

Field type Added meaning Intended Rails-profile lowering
attachment Uploaded file Active Storage
enum Closed value domain, optionally with semantic rank Profile-selected stored enum strategy
image Uploaded image with image-specific validation and optimized delivery Active Storage and Cloudinary
state_machine Stored state plus its transition graph AASM
counter Stored count maintained from one Association Audited Rails candidate: counter_culture
position Mutable contiguous order within a stored partition positioning
money Fixed-currency monetary value money-rails
rich_text Formatted text with embedded attachments Action Text
secure_token Secure generation, lookup, and form behavior Open
json One intentionally opaque JSON-compatible document PostgreSQL jsonb through Active Record

The gem or framework mechanism is not serialized. Except where the table labels a candidate or open path, a named mechanism is the Rails profile's current fixed lowering, not a choice the Compiler makes after approval. secure_token remains a format probe whose exact first Rails lowering is open. If a second supported lowering later creates a material tradeoff, the Foundation Plan can expose that choice then.

A derived Field keeps its ordinary result type and adds a typed rule for how that value is determined. Oscar Party's optional decimal movie.average_rating aggregates rating.score across movie.ratings. The current aggregate operations are sum, average, minimum, and maximum. A scoped Association supplies conditional membership rather than nesting another filter language inside the Field.

The dependency is ongoing. The derived value must reflect changes to a source value or to Association membership, and generated create and edit forms do not write it directly. The Field cannot also carry default or immutable.

Every derived Field needs at least one structured consumer before compilation. A Scaffold may display it directly. A Predicate, Ordering, or Association may consume it in a query. A detail-only consumer may permit a fresh record-level aggregate, while collection and query consumers require a set-based queryable value.

Oscar Party exercises the maintained case: its Movie index displays movie.average_rating and applies movie.highest_rated. One isolated Compiler projection records score-sum and Rating-count support columns intended for Counter Culture maintenance plus a stored PostgreSQL-generated result. It records production_wiring_complete: false, so neither part is selected Rails-profile output or generated-application support. The Plan still authors one semantic Field, but Decision 0024 requires a conventional Rails or common-gem comparison before production generation.

counter remains a separate common specialization. Its zero-initialized, nonnegative count and repair behavior are more specific than a general numeric aggregate.

json is for a structured leaf whose internal keys are not separate application-definition subjects. Shinar's translation.raw_response keeps the translation provider's payload for debugging and provenance. Its content, status, source version, and Language relationship remain explicit because application behavior depends on them. The type is not a shortcut for application-owned facts that Scaffolds, Predicates, Policies, Validations, or relationships need to understand.

The Rails profile lowers json to PostgreSQL jsonb. PostgreSQL recommends jsonb for most applications because it avoids reparsing and supports indexing. PostgreSQL json is useful mainly when preserving insignificant whitespace, object-key order, or duplicate keys is itself required. Those are storage details, so jsonb is not a second Plan type. Active Record deserializes both database types and reserializes values on write, so a product that needs the exact original body should store the raw input separately from its parsed json Field.

The Field catalog owns current type states, candidates, explicit deferrals, and rationale. Focused Issues can track active research or implementation without duplicating the catalog.

A counter Field names the Association it counts. A position Field names the Fields or References that partition records into separate lists; [] means one global list. A money Field names one uppercase three-letter currency code supported by the selected profile. Variable-currency money remains deferred because filtering, ordering, validation, and shared currency storage need more meaning than a currency column alone provides.

Its configuration census also exposed three different storage pressures:

The third category may need real identity and references in First Draft's authoring state; it should not become a blob. Type-specific configuration now lives under settings, and enum values are ordered { "subject_uuid", "key", "name" } objects rather than bare sibling strings. First Draft can normalize those value records for editing, history, and associations while keeping the approved artifact compact.

Two useful pressure tests are:

Requiredness, immutability, and generated forms

required and immutable answer separate questions:

Combination Stored meaning Default form eligibility
required, mutable The value is always present and may change. Create and edit
optional, mutable The value may be absent and may change. Create and edit
required, immutable Creation supplies the final value. Create only
optional, immutable Creation may supply the final value; omission fixes it as absent. Create only

The form column is an eligibility rule rather than a complete Scaffold definition. A user-authored immutable value may appear in scaffold.create.inputs. A value supplied by an associated parent, create binding, default, derivation, or target behavior may appear in neither form. State-machine, counter, and position Fields are normally changed through their generated behavior rather than raw scalar inputs. Each Scaffold still lists the eligible Fields and forward Associations it actually presents.

In the Rails profile, an immutable stored Field can lower to Active Record's attr_readonly. An immutable Reference applies the same protection to its backing foreign-key attribute or attributes:

class Bookmark < ApplicationRecord
  attr_readonly :user_id, :movie_id
end

The generated application should retain config.active_record.raise_on_assign_to_attr_readonly = true, which makes an ordinary persisted assignment fail with ActiveRecord::ReadonlyAttributeError. Rails 8.1 inherits that setting from its 7.1 defaults.

The controller and forms should make the same boundary visible. Oscar Party's Bookmark create form accepts a Movie, priority, and notes. Its edit form accepts only priority and notes. A create binding from current_account supplies user_id, so neither form accepts it. The controller can mirror those Scaffold definitions with separate parameter lists:

def create_bookmark_params
  params.expect(bookmark: %i[movie_id priority notes])
end

def update_bookmark_params
  params.expect(bookmark: %i[priority notes])
end

The ordinary Rails scaffold controller uses one parameter method for create and update. The Compiler shares one <resource>_params method when Create and Update accept the same parameter names with the same request handling. Control order and action authorization do not affect that comparison. Different allowlists or a Create branch that excludes a route-bound parent retain their action-specific helpers. A Create action with no editable inputs still omits its parameter method. Policy-owned attribute filtering remains a separate decision.

attr_readonly protects ordinary Active Record writes. Direct SQL and relation-level update_all do not instantiate models or run their validations and callbacks, so stronger database enforcement remains a separate Rails-profile decision. The initial immutable key does not include the rarer rule that a value may be filled after creation and then frozen. That outcome remains outside the Plan until generated applications demonstrate enough demand for another structured mutability rule.

Sensitive values and query use

A Field's type describes the shape of its value rather than how secret it is. A device push token is still short_text; it does not need an opaque_secret type. Two optional facts describe the current security needs:

These facts are independent. They often appear together, as they do on Shinar's translation.raw_response, but neither changes the Field's value type. That Field is an opaque json provider payload rather than a credential, which shows that the two properties describe handling rather than secrecy: a response body kept for debugging is exactly the kind of value that reaches a log by accident. A Field that needs neither property normally omits both rather than carrying false-valued boilerplate. Both properties accept explicit false and default to false, so omission and explicit false have the same meaning.

For the Rails profile, encrypted_at_rest can lower to Active Record Encryption. On an emitted ordinary scalar or admitted required enum Field, redact_from_logs appends the attribute to self.filter_attributes and registers the model-qualified request parameter. Case Chat filters notification.deduplication_key, while the same key under another model remains visible. A requested modifier on every other or unemitted Field remains a gap. Rails 8.1 adds encrypted attributes to both automatically by default. The explicit Plan facts still matter because they describe the required outcome rather than relying on a framework default remaining unchanged.

Neither property says whether the application queries the Field. Query use is expressed where it occurs: a uniqueness Validation names its targets, while a Predicate or Ordering names its operands. The first standard Scaffold uses the generated system ID for record routes. A future slug or alternate route locator would be its own structured use rather than a Field-level used_for_lookup Boolean, which would lose whether a lookup is global or scoped and single-Field or compound. Each queried Field supplies its own comparison behavior.

If one of those definitions queries an encrypted Field, the target may need another material realization choice. Equality lookup might use deterministic encryption, a keyed digest, or another supported strategy. That choice belongs at the narrowest subject that can describe the complete lookup. The Compiler should not infer it from encrypted_at_rest alone. Shinar's device registration and token refresh remain with the user's agent, so its current structured push-token Field does not yet claim a lookup strategy.

Relationships, queries, validations, and workflows

Each of these subjects has its own owner page:

Other subjects already pressured by the examples

The examples also use three provisional concepts that need only enough structure to drive the current probes:

The recovered vocabulary also proposed a general Automation subject for causal work. Shinar showed that its draft shape hid an imperative program behind JSON without fully describing triggers, retries, or authorization-safe live delivery. Automation is therefore not a current structured subject. A narrower causal primitive can earn a place when an example gives the Compiler a closed shape it can generate.

The optional Account property owns authentication identifiers, sign-in methods, registration, verification, recovery, and lockout. Its semantic identity comes from its containing Entity. The current sketch allows at most one Account-bearing Entity. Authentication-managed columns are a Compiler consequence rather than ordinary Fields; product values such as a display name and preferred Language remain User Fields even when registration initializes them.

The current Project graph follows that identity boundary: Account has an ordinary internal row key but no subject UUID, and its ordered identifier and sign-in-method value children have no subject identity either. All three row types carry direct Project ownership with composite same-Project links. Registration inputs are not stored as pending readable locators; their later import slice must resolve directly to a creatable Field or the canonical derived forward Association.

Shinar can compare a User Reference with { "kind": "environment", "name": "current_account" } or follow a typed environment_path Value from that User. Dunbar150's signed-in User and public Profile are different records. Treating the Profile as another runtime identity would add a second account-relative path through the whole model, so its interlinked Profile defaults, Policies, and custom routes remain outside the Plan for now.

The first Scaffold vocabulary is intentionally narrower than the earlier application-wide Screen model. Its nonempty resource_routes list selects only index, show, new, create, edit, update, and destroy. An index or displayed associated collection may apply a Predicate and Ordering at that use site. A direct associated collection can reuse its target Entity's scaffold.create definition through scoped New and POST routes, even when no top-level create route is selected.

Create and edit remain separate routes but share their Entity's create and update definitions. Those definitions list ordered user-selected Field and forward-Association inputs. An associated parent is supplied from the Scaffold record context. Ordered create bindings supply other server-owned values, such as assigning a User Reference from current_account, before authorization and persistence. Jobs, tasks, tests, and other model callers remain explicit because this is not an Active Record default.

Mutation forms and destroy controls use conventional destinations derived from their interaction context. Optional return_to authors a product exception. Screens (repository-only) owns the defaults, supported resource overrides, and current-location gap. The Rails target emits known destinations directly. Scoped associated forms derive the parent from the URL and submit no parent or form-context fields. Navigation consumes no submitted return URL, Referer, or session history.

The Plan does not currently expose arbitrary UI actions or State Machine event controls. Modal and sheet presentation also remain renderer concerns. Richer binding research stays deferred until standard resource routes prove insufficient; Scaffolds (repository-only) keep that revisit trigger.

Shinar keeps Translation structured because it has identity, a target Language, a source version, lifecycle state, and compound uniqueness. Its Foundation timeline is limited to message.original_content. Viewer-dependent Translation selection, fallback precedence, and deleted-message presentation remain with the user's agent.

Bespoke commands and Policy or route behavior outside the supported shapes remain outside the Plan rather than entering a weak structured expression language. Their exact record shapes remain open.

Any future structured Automation would need enough closed meaning for deterministic lowering. Until then, the user's agent retains the outcome, trigger knowledge, constraints, and acceptance signals. The Compiler does not derive typed dependencies or Capabilities from that unstructured context.

Reference data and development data

The current direction distinguishes two kinds of literal records:

Plan key Meaning Environments Owner
reference_data Stable facts the application needs to operate Every environment One Entity
development_data Disposable records for exploring the application Development only Application

Shinar's supported Languages are reference data. A deployed Shinar installation needs the same Language rows before a User can choose a preferred Language:

{
  "reference_data": {
    "identity": {
      "field": "language.code"
    },
    "records": [
      {
        "subject_uuid": "019f9425-5412-754f-91aa-620c09b2dcb6",
        "key": "english",
        "fields": [
          {
            "field": "language.code",
            "value": {
              "kind": "literal",
              "value": "en"
            }
          }
        ]
      }
    ]
  }
}

identity names the Field used to distinguish and reconcile the records. Each record has a Project-scoped subject_uuid and an owner-local key; another Plan value points to the readable entity.key record path. Shinar's registration default is therefore:

{
  "kind": "reference_record",
  "record": "language.english"
}

That readable path exists only inside the Foundation Plan. It does not become the Language primary key or add another application Field.

Oscar Party's movies, credits, watchlist, ratings, and Alice's usable Account are development data. These records form one application-wide graph because a Bookmark needs to link a development User to a development Movie:

{
  "development_data": [
    {
      "subject_uuid": "019f9425-5412-791d-9a00-b4e377c3086c",
      "key": "alice",
      "entity": "user",
      "fields": [
        {
          "field": "user.name",
          "value": {
            "kind": "literal",
            "value": "Alice"
          }
        }
      ],
      "account": {
        "identifiers": [
          {
            "kind": "email",
            "value": "alice@example.com"
          }
        ],
        "credentials": [
          {
            "kind": "password",
            "value": "password"
          }
        ]
      }
    },
    {
      "subject_uuid": "019f9425-5412-74f9-a734-8566129f9d4d",
      "key": "alice_conclave",
      "entity": "bookmark",
      "references": [
        {
          "reference": "bookmark.user",
          "value": {
            "kind": "reference_record",
            "record": "user.alice"
          }
        },
        {
          "reference": "bookmark.movie",
          "value": {
            "kind": "reference_record",
            "record": "movie.conclave"
          }
        }
      ]
    }
  ]
}

The application omits development_data when it has no development records. A reference_data object is optional because an empty object could not supply its required identity Field.

Both forms use explicit tagged Values. Field assignments admit literals rather than Faker calls, factories, arbitrary Ruby, or runtime values such as current_account. Reference assignments use reference_record Values to name other data records. Field defaults, State Machine initial states, normalizations, and derived behavior still apply when an assignment is omitted. A tagged literal is a contextual carrier, not evidence that every Field kind has a static representation: rich text uses a string and json preserves one opaque JSON value, while attachment and image data remain unsupported until an asset-reference Value is designed. A path or URL string does not silently become an uploaded file.

The Compiler derives creation order from the named Reference targets instead of treating array order as dependency order. Whole-Plan validation still needs to prove that:

The current sketch/0.23 port separates Data normalization into pure planning slices. DataTopologySlice projects development and reference Data Sets plus ordered Data Record headers. It resolves the explicit development Entity and reference identity-Field links to subject UUIDs. DataAssignmentSlice then resolves Field and Reference assignments to stable subject UUIDs. It rejects missing, repeated, cross-owner, and Reference-target-incompatible links and keeps each tagged literal deeply immutable. Both slices return source-addressed diagnostics without touching Active Record. The live Postgres graph persists the normalized set and record headers plus both ordered assignment kinds. Assignment rows retain resolved relations and exact tagged literals rather than raw locator payloads. Protected links are deferred so an atomic edit can replace them without allowing a deletion to erase authored assignments. A development Data Record may also retain the Account-bearing Entity's exact declared identifier and credential payload in one filtered JSONB attribute. Replacement compares that payload as part of the normalized Data graph, while Active Record inspection filters the whole nested value.

The Service port persists development Account data with the normalized Data Record and includes it in idempotent graph replacement. The authored Plan and generated development seed intentionally retain these known local credentials; model and captured-graph inspection plus ordinary parameter, SQL, and structured-bind log filtering redact the persisted nested payload. After rendering, ordinary inspection and pretty-printing of generated files, manifests, artifacts, and captured-project results redact generated source bytes. This is inspection and logging hygiene, not encryption or a general secrecy guarantee; direct access to the owner-only source document, persisted attribute, generated-file contents, and artifact source bytes remains functional.

General target-independent comparison-aware identity-value and Account-identifier uniqueness analysis remains a later pass. The bounded Rails development-seed admission separately checks exact development Account email collisions and conservatively compares admitted lookup and uniqueness tuples with their selected target-storage semantics. Offset-equivalent datetimes share an instant; values a full microsecond apart are distinct. Realized citext comparison proceeds component by component, proving a result whenever locale-stable ASCII comparison is decisive and leaving only locale-sensitive ASCII or non-ASCII outcomes unproved. Canonical decimal literals are already injective across admitted numeric values. When the target cannot prove two candidate values distinct, it retains the first deterministic record, omits the smallest later record or assignment, reruns dependency closure, and preserves every consequence in the GapSet.

Development credentials are deliberately known, non-secret values. The generated repository may document them so a developer can sign in immediately. They are never loaded outside development and must not contain an owner or production secret.

The generated README describes sample loading only when the selected development seed residual is nonempty. When that residual includes a realized Account with usable email/password data, it also lists /login and those initial disposable credentials. A requested Account or data record that is omitted does not produce a login claim. The README distinguishes first-seed credentials from an existing password, which repeat seeding preserves. These instructions describe the emitted result; a populated first preview still needs browser and sign-in verification.

The data shapes deliberately do not say whether application users may edit rows or how a later compilation reconciles them. Policies and Scaffolds describe user access. Compiler setup and recompilation need their own idempotence rules. The removed replaceable Boolean conflated those separate questions.

The bounded Rails implementation follows the environment split documented by Thoughtbot's current seed-data guidance and Suspenders. Rails Core owns db/seeds.rb; it loads the matching db/seeds/<environment>.rb when that file exists. The Compiler replaces Core's empty db/seeds/development.rb only when it proves a nonempty development residual. The emitted file uses stable target attributes and References in ordinary find_or_create_by! calls, creating dependencies first. Readable Ruby locals connect records and support required follow-up work. An existing ordinary match returns without assigning attributes or running save callbacks again; immutable attributes and References are supplied when creating the row. Account seeds retain their login lookup, mutable setup, creation-only immutable values, and missing-password repair. Seeds reach authored State Machine values through generated events. When an event path overwrites an explicitly authored Field value, the seed restores that value after the complete path, only when the path runs.

Generated seed-file provenance includes the selected enum or State Machine FieldValue whenever a non-nil literal chooses one, including a required State Machine's initial value. An optional nil State assignment emits no transition and therefore has no selected-value subject to claim.

Reference-data lowering remains a direction. The current development-data slice does not add a custom task, scenario registry, deletion pass, or reconciliation promise for an already-edited application. Ordinary db:seed:replant can rebuild the disposable development database from the same generated file.

The semantic record keys can become local names in generated seed source. They are not persisted merely to make setup work. Test fixtures and factories remain generated test support, not a third kind of Foundation Plan data. A future production demo mode or multiple named development scenarios would need separate evidence before adding another serialized concept.

Authoring persistence and serialization

First Draft plans one mutable, normalized Project-scoped graph for editing, referential integrity, analysis, ERD projection, and queries. The graph is live authority. Deterministic Foundation Plan serialization is the external agent contract and fixture shape; the Compilation lifecycle now captures exact Plan snapshot bytes and provenance as immutable evidence at compile start. A separate operational recovery snapshot remains unimplemented. The serialized forms are not a second authored graph. The implemented FoundationPlan::Head separately retains the latest accepted PUT bytes, format, and digest for representation concurrency. It is mutable transport evidence, not a recovery snapshot.

The working authoring shape is:

Project
  ├── selected stack
  ├── graph_version
  ├── current Head
  ├── Entity
  │   ├── Account
  │   ├── Field
  │   ├── Reference
  │   ├── Association catalog (authored and Reference-derived)
  │   ├── Predicate
  │   ├── Ordering
  │   ├── Validation
  │   ├── Tree
  │   ├── Policy
  │   └── Scaffold
  ├── development data
  ├── sparse realization choices
  ├── Analysis Runs and accepted derived projections
  ├── planned recovery snapshots (not implemented)
  └── Compilation Records and captured Plan snapshots

Every authored row has a direct Project scope, an ordinary internal surrogate key, and—where the document exposes subject continuity—a Project-scoped subject_uuid. The serialized Foundation Plan nests subjects by semantic ownership and can inline realization choices beside them even if First Draft stores those subjects and choices as separate rows with real foreign keys.

A Project-owned subject-identity registry gives all 12 current subject kinds one UUID namespace. Entity, Field, FieldValue, Reference, StateTransition, Predicate, Policy, authored Association, Data Record, Tree, Ordering, and Validation rows claim that registry through typed composite foreign keys. Deleting one of those concrete rows through the supported lifecycle retains its UUID-to-kind claim, so the same kind may be restored while a different kind cannot reuse the UUID. Reference-derived forward Associations carry no Association subject UUID and make no claim. The complete importer still owns source-addressed conflict diagnostics and bulk claims across the whole document. Registry records are append-only through Active Record; raw relation or SQL mutation remains outside database enforcement.

Typed links in the serialized document use readable scoped paths. Import resolves those paths to same-Project surrogate foreign keys; the database does not persist them as relationship identity. Renaming a subject keeps its subject_uuid and existing relational links, while deterministic serialization derives links from the current readable keys.

Names and namespaces

Earlier work discussed Project::Schema::Entity, AppSchema::Entity, and short table prefixes. The current one-graph direction removes the need for parallel App Schema and Foundation Plan record namespaces. Ported target-specific analysis belongs under FoundationPlan::RailsTarget, which avoids introducing a lexical Rails constant that competes with ::Rails.

The implemented graph through the Association catalog now uses Project, FoundationPlan::SubjectIdentity, FoundationPlan::Entity, FoundationPlan::Field, FoundationPlan::FieldValue, FoundationPlan::Reference, FoundationPlan::ReferenceTarget, FoundationPlan::StateMachine, FoundationPlan::StateTransition, FoundationPlan::StateTransitionSource, FoundationPlan::StateTransitionEffect, FoundationPlan::Predicate, FoundationPlan::Policy, and FoundationPlan::Association. It does not settle names for the remaining authored or run-scoped records.

Questions for the fixtures

  1. Does separating Reference storage from every Association reader remain clear through renames and omitted target-side accessors?
  2. Which choices belong on an Entity-owned subject, and which genuinely need application-wide scope?
  3. Which nested authored values need independent subject continuity rather than identity through their owner?
  4. Which two or three Field types force a real Type Object persistence design?
  5. Is a typed transition callback narrow enough to generate safely without rebuilding a command language?
  6. What repeated application pressure would justify adding State Machine controls to generated Scaffold surfaces?
  7. Which repeated causal behavior, if any, earns a narrow structured primitive after general Automation failed?

References marked “repository-only” name implementation or internal material outside this public guide. They are intentionally not links. Publishing a design does not prove its implementation.