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:
- scalar leaves such as length, precision, currency, or a default;
- small literal lists such as allowed content types; and
- relational configuration such as ordered enum-value records or a slug's source Field.
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:
- If a value needs identity, ordering, metadata, state, or several instances, it may be an Entity rather than a
more elaborate Field. Dunbar150's ordered
PostMediaItemand Shinar'sTranslationare good examples. - Type names in a Plan should be semantic tokens such as
email,money, orimage, not Ruby class names.
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:
encrypted_at_restasks the target to protect the stored value while preserving the application's ability to read it; andredact_from_logssays generated request logging, model inspection, and diagnostics must not expose the value.
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:
- References and relationship edges owns References, closed target sets, Associations, cardinality, deletion outcomes, and Trees.
- Query intents owns Predicates, Expressions, Orderings, and the parameterized-Predicate ladder.
- Validations owns the closed Validation registry, error targets, and uniqueness.
- State-machine Fields (repository-only) owns states, transitions, applicability, and effects.
- Field catalog owns the deferred conditional-mutation shape.
Other subjects already pressured by the examples
The examples also use three provisional concepts that need only enough structure to drive the current probes:
- an Account property marks its containing Entity as the record that signs in and describes authentication;
- a Policy says whether a named operation is allowed in the current request context using one typed Policy Expression; and
- an Entity-owned Scaffold selects standard resource routes plus the recursive projections, inputs, bindings, authorization, and return destinations used by generated behavior.
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:
- every development-data record names an existing Entity, and every record key is unique in that Entity's scope;
- every Field and Reference assignment belongs to that record's Entity;
- every assigned value is compatible with its Field type;
- each Reference target exists and belongs to one of the Reference's target Entities;
- each reference-data record supplies its identity Field with a unique, non-null literal value, and that identity Field is queryable;
- no assignment writes a Counter or aggregate Field;
- reference data does not depend on development data;
- required Reference links between data records form no cycle, while optional links may be applied after creation;
- no record omits a required creation value, relies on a request-bound default, or normalizes required text to null;
- only a record of the Account-bearing Entity carries
account; and - account identifiers and credentials match the methods declared by that Entity, each declared kind exactly once.
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
- Does separating Reference storage from every Association reader remain clear through renames and omitted target-side accessors?
- Which choices belong on an Entity-owned subject, and which genuinely need application-wide scope?
- Which nested authored values need independent subject continuity rather than identity through their owner?
- Which two or three Field types force a real Type Object persistence design?
- Is a typed transition callback narrow enough to generate safely without rebuilding a command language?
- What repeated application pressure would justify adding State Machine controls to generated Scaffold surfaces?
- Which repeated causal behavior, if any, earns a narrow structured primitive after general Automation failed?