Skip to content

AppBuildPlan requirement inventory

Audited base: 7211107d1da35021789c23b1a6f9e927ff6a76ed.

Classification follows draft ADR 0012's construction/judgment distinction, collapsing its approved-fact A into C only when the approved input exists. C means one answer follows from approved input and an existing construction rule. J means choosing a semantic realization, ownership resolution, scope change, optional output family, provider, or dependency target would require judgment. A mechanically checked prohibition is still listed as a C guard: there is no positive omitted structure to construct, and deleting/changing the forbidden candidate would require judgment. A route collision in approved input is an input error; silently changing the approved route is not construction.

review below means factory_app/workflows/AppGenerator/tools/app_plan_review.py; cache means factory_app/workflows/AppGenerator/tools/app_build_plan.py. References are validator/helper symbols so they remain useful after line numbers change. The Added column records this change; retained guards have no missing positive output to construct.

Requirement (source) Class Already constructed at base? Added / disposition
At least one materializing build task (review.validate_plan_coverage) C for closed greenfield inputs; J for revision/adoption scope Partly: trios only when some task already exists; subscription task independently Yes: removed empty-task short-circuit; page/module construction runs on an empty task list; revision/adoption boundary retained
Preserve every approved page name and route (validate_plan_coverage) C Yes, _repair_coverage, but skipped when task list empty Yes: shared _approved_page_inventory, including empty-task plans; restored pages also copy available approved purpose/design/action metadata
Planned page routes unique (validate_plan_coverage) C guard; upstream collision needs J Normalizing to distinct approved inventory fixes model-only duplication; conflicting approved routes remain rejected No route guessing; retain rejection
Route-derived page filenames unique (validate_plan_coverage) C guard; upstream collision needs J No Retain rejection; cannot rename approved routes/files arbitrarily
page_bundle union owns app.json and all route-derived ui/pages/{stem}.yaml paths, exact case (validate_plan_coverage) C No absent task construction; existing page tasks get paths Yes: missing aggregate page task constructed; _required_page_paths is shared by constructor and validator; absence of any approved/proposed page remains unresolved page scope
Page path assignment has no competing owner; stale model-only page files removed (_repair_coverage, cache._validate_unique_owned_paths) C for page bundle canonical ownership; J for conflicting semantic owners Existing page-task assignment/deduplication and empty-task deletion; conflict with other task types not resolved Keep established page owner where present; reject conflicting non-page owner; do not create duplicate paths
Each approved app-owned module has exactly one module capability (validate_plan_origins) C for absence under existing generated-module policy; J for competing implementations Yes, _repair_plan, with absence/multiple-claim guard Keep absence construction; reject competing claims
Generated module capability ID equals approved surface ID (validate_plan_origins) C Yes, _repair_plan Retain; do not use product categories as IDs
Generated module primary entities exactly match approved surface ownership (validate_plan_origins) C Yes, _repair_plan Retain approved copying
Generated module has module_contract owning modules/{id}/module.yaml (validate_plan_coverage) C Yes, _repair_coverage, except empty-task input Yes: shared _required_module_paths, including empty-task plans
Generated module has data_models owning modules/{id}/backend/schemas.py (validate_plan_coverage) C Yes, except empty-task input Yes: shared _required_module_paths, including empty-task plans
Generated module has business_services owning handler/service (validate_plan_coverage) C Yes, except empty-task input Yes: shared _required_module_paths, including empty-task plans
Module owning approved entities additionally owns repo/policy (validate_plan_coverage) C Yes, except empty-task input Yes: shared _required_module_paths, including empty-task plans
Explicit user_data_scope=true additionally requires account-data handler (validate_plan_coverage) C after scope is supplied; scope choice is J Yes for a supplied true flag, except empty-task input Yes: same shared requirement computation on empty tasks; no new inference of user-data scope
All task/capability surface IDs belong to approved design, selected provider/facade, or required subscription scope (_validate_plan_surface_inventory, called before repairs and in origins) C guard Already rejected before repairs (#735) Pre-repair rejection retained; new aggregate page scope is only page_bundle/ui_only/null-pack and approved page paths plus app.json, never capability/module/service authority
Managed/framework/operator sources require registered packs (validate_plan_origins) C registered fact; selecting a replacement implementation is J Partly: _repair_plan converts unsupported source on an approved app-owned module to generated module; unapproved scope rejected first Preserve registration boundary; do not guess an unselected replacement pack
Registered source equals registry source (validate_plan_origins) C Partial: downstream fills absent managed source, but review rejects a conflicting one before downstream Yes: _repair_selected_pack_sources copies registered source before origins validation
Every module-path task names exactly one declared capability (validate_plan_origins) C when approved surface and path agree; J if competing candidates _repair_module_task_capabilities and _repair_plan; downstream also infers from paths Keep agreement-proven repair; unknown or competing capability remains feedback
Module-path task writes one module, and directory ID equals capability ID (validate_plan_origins) C when naming drift is proven; J when multi-module intent/partition is unresolved _repair_plan rewrites known renamed directories; no arbitrary split Preserve resolved rewrite; reject unresolved mixed modules
Task surface ID equals its capability surface ID (validate_plan_origins) C when capability binding is unambiguous Existing rename/facade repairs copy it; disagreement stays rejected Existing closed-binding repairs retained; explicit conflicting approved owners remain feedback
Task IDs nonempty and globally unique (validate_plan_dependencies / deterministic_topological_order) C for one known scope/type; J for duplicate candidates in same lane/unknown partition Collision repair only, via _repair_task_identities; task synthesis uses _available_task_id Yes: blank IDs now use the same scope/type allocator and reference rewrite; ambiguous duplicate candidates still rejected
depends_on entries name existing task IDs (validate_plan_dependencies, cache._validate_task_dependencies) C for constructor-owned prerequisite edges; J for unresolved arbitrary references Renames rewrite known references; required module/page edges appended; unresolved references rejected Reuse actual constructed IDs; do not drop unknown dependencies to obtain green review
DAG has no self-dependencies or cycles (validate_plan_dependencies) C guard; resolving an authored cycle is J Constructor edges obey intended DAG; authored invalid graph rejected Retain rejection, including after cache mutations
depends_on is list when present (cache._validate_task_dependencies; typed schema earlier) C schema guard Schema validation rejects invalid type No semantic coercion
Models directly see own module contract; business services see own contract and schemas; persistent modules see selected persistence contract (_repair_module_task_dependencies) C construction policy (not independently enforced by named validators at base) Yes for generated trios Keep and share direct-owner IDs, including newly synthesized tasks
Page tasks directly see every selected module contract (_repair_page_contract_dependencies, cache._normalize_page_task_dependencies) C conservative visibility policy Yes when page task exists Apply after missing page task construction
Facade contract sees the managed client it calls (cache._normalize_facade_task_dependencies) C only with explicit selected facade/provider binding; J for prose-only inference Downstream guesses by prose or the sole available adapter Yes: registered facade/provider bindings now own inferred prerequisite edges; prose/sole-adapter fallback removed; competing adapters require explicit dependency
No task owns config/subscriptions.yaml; assembly writes it from the approved subscription contract (release_subscriptions_config in review and the plan cache; materialize_app_config_contracts) C Formerly a synthesized subscription_config task whose output assembly overwrote Task type, ConfigMiddleware mode, and file contract removed; a task that lists the path loses it, and one left empty is dropped (the #765 pack-owned pattern)
Every owned path passes task dispatch's normalize_owned_path (review._apply_dispatch_path_rules, first after the strict model) C for a pattern; J for an absolute, traversal, or secret-term path No: review checked is_safe_app_path, which accepts *?[; dispatch refused the plan and ended the run (chat e67150d7 at 49c69860) A pattern names no file and a module's contracts/ companions are already optional outputs of its module_contract task, so it is released and a task left empty is dropped with its edges; every other refusal is rejected with dispatch's own message
No task owns a path in a selected pack's facade module directory except one the pack leaves to a model or the workspace (release_pack_facade_paths in review and the plan cache; _authored_module_paths; validate_plan_coverage) C Only the declared required_outputs were released (#765); planner files beside them (backend/repo.py, contracts/events.yaml) stayed model work pack_facade_directories reads facades from the selected pack contracts. Undeclared paths are released, a task left empty is dropped with its edges, and coverage requires nothing there; an owner: generator output, or a workspace file after genesis, is exempt
The cached batch items pass dispatch's owned-path preflight (review.validate_plan_dispatch, last before ready) C guard No: review and dispatch used different owned-path rules Runs dispatch's _validate_batch_owned_paths over _normalize_task_items for each AppPlanAgent batch, per task and then together, and rejects with one message naming every refusal
Every task type belongs to _ALLOWED_TASK_TYPES; retired agent-backend/admin types forbidden (cache._validate_build_tasks) C guard; choosing unknown task intent is J No coercion; schema/validator rejection Preserve bounded rejection
Task type maps to canonical initial worker; pages use AppSchemaAgent (cache._validate_build_tasks) C Synthesis only; existing wrong initial agent rejected Yes: _construct_task_requirements applies _CANONICAL_INITIAL_AGENTS to existing tasks as well as synthesized tasks
Workflow surfaces cannot be AppGenerator build tasks (cache._validate_build_tasks) C guard; appropriate workflow touchpoint binding is J Rejected Do not turn workflow tasks into page/module tasks
external_integration→api_surface/service_foundation; refinement→refinement_harness; ui_only→page_bundle (cache._validate_build_tasks) C when the selected task intent is closed; J if proposed task and approved surface conflict Only synthesis emits correct pair; conflicts rejected Canonical fields for known constructed lanes; retain unresolvable mismatch rejection
Managed provider may not own module_contract or modules/{provider}/ (cache._validate_build_tasks) C guard; provider/facade split C from selected contract Existing MozaiksPay facade repair and downstream pack expansion Yes: selected facade expansion now precedes coverage so its trio is constructed; provider-internals guard retained
Managed provider client may not use services/adapters or service_foundation; api_surface is external_integration (cache._validate_build_tasks) C boundary once provider selected Existing MozaiksPay adapter scope repair; declared client templates independently materialized Yes: general registered managed api_surface scope canonicalization; forbidden lanes still rejected; no redundant client task synthesized
Module-local tasks require nonnull pack and only paths under that module (cache._validate_build_tasks) C guard; recovering unresolved binding is J Known-label repair and downstream path inference Repair closed scope only; reject cross-scope or unbound tasks
module_contract owns no backend paths (cache._validate_build_tasks) C guard; moving arbitrary proposed work may need J Rejected; mandatory module files constructed in separate lanes Do not silently delete or redistribute undeclared optional files
data_models owns only backend/schemas.py (cache._validate_build_tasks) C required path plus guard Coverage adds schema; arbitrary extra paths rejected Shared schema requirement; retain invalid extra-path rejection
business_services owns backend paths excluding schemas.py (cache._validate_build_tasks) C required paths plus guard Coverage adds known required files; arbitrary extra paths rejected Shared service requirement; preserve bounded optional-helper selection
data_migrations requires pack and paths under data/migrations/ (cache._validate_build_tasks) C path boundary; migration selection/ID/content is J No missing migration construction; invalid scope/path rejected Retain rejection; do not infer migration from new entities (validator checks prefix, not .json suffix despite error text)
Managed api_surface client must name its registered pack (cache._validate_build_tasks) C only when selected typed binding is known Downstream infers pack from registered <pack>_client.py filename Prefer selected typed client binding; arbitrary filename inference is not selection authority
No deployment artifact task output; export renderer owns it (cache._validate_build_tasks) C guard Rejected Keep rejection; no missing build task to add
No raw persistent frontend source (cache._validate_build_tasks) C guard; declarative realization of arbitrary UI request is J Rejected Keep schema-page construction; do not translate arbitrary proposed source paths
No obsolete app/config/admin.json (cache._validate_build_tasks) C guard; intended admin semantics are J Rejected Keep rejection
Split service admin pair uses api_surface, ControllerAgent, null pack, and both admin_config.py/routes/admin.py (cache._validate_build_tasks) C after one approved admin output selects the lane; admin feature selection is J No pair completion; existing invalid pair rejected Yes: _required_selected_task_paths completes selected api_surface pair; canonical worker and null pack; unrelated invalid paths/types remain rejected
No retired data/security app paths (cache._validate_build_tasks) C guard; recovering intended modern semantics may need J Rejected through canonical runtime path helpers Keep rejection; do not rename unknown data/security payloads
Paths remain within canonical app planes/root files (cache._validate_build_tasks) C guard Rejected via runtime canonical-path helper Keep rejection
Config paths belong to canonical config files/metadata (cache._validate_build_tasks) C guard Rejected via runtime canonical-path helper Keep rejection
Services paths use canonical integrations/adapters/routes/config/admin_config lanes (cache._validate_build_tasks) C guard Rejected Keep rejection
service_foundation owns only services support lanes and optional security/secrets.yaml (cache._validate_build_tasks) C guard; whether those files are needed is J Rejected; no mandatory service-foundation file set No speculative task/files
Refinement task null pack, refinement surface, permitted config/prompts paths (cache._validate_build_tasks) C after explicit selection; selection/contents J Validated only Yes: canonical selected-task fields filled when owned paths are valid; forbidden paths remain feedback
Selected refinement task must own config/refinement_policy.yaml and refinement_harness/config/harness.yaml (cache._validate_build_tasks) C No mandatory-pair completion Yes: shared _required_selected_task_paths and constructor completion
Owned artifact paths exclusive across tasks (cache._validate_unique_owned_paths) C guard; competing semantic partition is J Page assignment and persistence merge only; other conflict rejection Construct only unowned required paths; preserve unrelated conflict rejection. Base expressly exempts app.json from this guard; dispatch does not, so validate_plan_dispatch rejects a revision that shares it
Pages bind to facade rather than managed provider/backing module endpoints (cache._validate_page_bindings) C when action mapping declared by selected facade contract; J when no mapping exists _normalize_managed_capability_page_bindings rewrites declared provider action mappings Retain exact contract mapping; unmatched typed module/action references rejected
User-facing managed capability has a page task and non-provider module_contract (cache._validate_user_facing_managed_capability_tasks) C for explicit primary_pages/facade contract; prose token heuristic is not approved input No page task construction; facade capability/page expansion occurs downstream Yes: missing page task and registered selected facade trio constructed before validation; unknown facade realization still rejected
monetization_provider is set exactly when the approved subscription contract has contract_required=true, read from subscription_contract or its artifact fallback (cache._validate_monetization_provider_selection) C when explicit approved subscription/provider selection exists; absent selection J Keyed on the approved contract, not on a planned task _resolve_monetization_provider resolves a selected provider pack; an unchosen provider stays feedback
MozaiksPay and entitlement_dispatch cannot both be selected; ≤1 managed subscription-write-path owner (cache._validate_monetization_provider_selection) C guard; choosing among conflicting providers is J Rejected Keep rejection
mozaiks_pay requires selected mozaikspay and excludes entitlement_dispatch (cache._validate_monetization_provider_selection) C from explicit selected provider; conflicting selection J Downstream adds default descriptor if provider chosen Yes: protected selected registry entries copied before coverage; contradictory providers remain rejected
entitlement_dispatch requires its pack and excludes managed subscription-write-path owners (cache._validate_monetization_provider_selection) C from explicit selected provider; conflicting selection J Validation only; no automatic entitlement pack choice Yes: explicitly selected framework/operator packs now copied along with managed packs; unavailable provider choice remains feedback
carry_forward_decisions entries are objects with module_id, allowed decision, nonempty reason, valid optional source (cache._validate_carry_forward_decisions) J for the decision/reason; C shape guard only Validation only No fabricated baseline/decision/reason/provenance
Carry-forward affected task list references existing tasks (cache._validate_carry_forward_decisions) C for known constructed/renamed tasks; J for unresolved target Collision identity repair rewrites references; unknown references rejected Preserve exact references; no arbitrary reassignment
App kind nonempty (cache.app_build_plan) J unless approved classification supplied Rejected Do not infer app kind from task/module labels
At least one page (cache.app_build_plan) C if approved experience has pages; otherwise J missing scope Approved-page restoration exists except empty-task input Yes: restore approved experience on empty-task input; missing approved/proposed page semantics remain feedback

Limits and decisions from the inventory

  • validate_plan_coverage skips all greenfield inventory/file requirements for revision or brownfield after requiring at least one task. Extending missing-task construction to those modes without an approved delta/baseline would be a semantic expansion; do not silently do it.
  • The three named validators do not require a persistence_contract task merely because entities or a data_contract exist. They also do not independently mandate a client task merely because a managed provider exists. Required managed template outputs already materialize independently through assemble_app_tasks._apply_managed_capability_templates → resolve_managed_capability_templates, using canonical resolve_declared_pack_output_paths. Those outputs are C/already constructed by assembly; they do not prove a missing task requirement.
  • app_build_plan remains a mutating normalization/cache stage: it merges persistence tasks, resolves explicit/selected subscription-provider choices, adds selected facade capabilities/pages, rewrites bindings and dependencies, and sorts tasks. This change prepares selected inventory before required-task construction and also rechecks origins after caching; it does not claim the full frozen-plan producer replacement proposed by ADR 0012. Existing registered adapter-filename inference is retained, not newly justified as approved judgment.
  • _ensure_context_selected_capability_packs formerly iterated a union of protected selected capability_packs and available_managed_capabilities. It now copies only protected selected entries, including framework/operator packs. Availability alone does not select an omitted provider.
  • Removed _repair_missing_read_operation and _plural_entity_slug, which invented list_<plural first entity>. Approved owned_mutations now populate missing capability operations and the task worker's action list; explicit model operations survive. There is no read-prefix completeness rule. Unknown action semantics remain with the existing bounded planning/worker validation feedback; this change does not fabricate a read requirement merely because entities exist.
  • Removed _facade_adapter_dependency's free-prose and sole-adapter inference. Registered selected facade/provider bindings now supply these edges. Competing provider or adapter bindings receive bounded feedback rather than a guessed edge.
  • Removed subscription-task-only provider defaulting. An explicit provider or already selected provider pack resolves the identity; an otherwise unchosen provider remains bounded validation feedback. Synthesized pack category defaults now use legal schema taxonomy values (custom_domain, or the known mozaikspay category), not source labels such as managed_capability.
  • Path/worker prohibitions are listed completely above, but they are guards rather than demands to construct missing optional functionality. A task asking for forbidden output does not authorize guessing an alternative feature or deleting the request.
  • execution_target is declared in the typed model but not independently validated in app_build_plan; valid plans use AppGenerator. Initial-agent repair comes from _CANONICAL_INITIAL_AGENTS; it is a separate requirement from workflow execution target.
  • Passing current review/cache validators is not strict AppBuildPlan model roundtrip validation: existing cache enrichment carries registry-only metadata and page projections with a different shape, while synthesized module descriptors omit model-authoring descriptive fields. The strict model validates the candidate before repairs, not the enriched cache afterward. This task closes enforced structural requirements; it does not claim to reconcile every producer/cache schema distinction proposed by ADR 0012.
  • The three identical raw live model responses were recovered during implementation. tests/fixtures/appplan_monetized_missing_structure.json preserves the model payload and projects the persisted approved context with local metadata normalized. The actual approved design includes an app-owned auth surface; this change preserves that approved input and does not claim to reclassify its upstream product decision. Invented surfaces outside that approval are still rejected.

The exact base behavior is checked by running the new tests against 7211107d: the omitted-page live replay and minimal omitted-structure cases fail there, while complete-plan preservation and unapproved-surface rejection already pass. See the PR validation results for exact case counts and command output; these are offline review/queue replay tests, not a fresh live generated-app acceptance run.

Adjacent typed and runtime safeguards

Public review validates the strict AppBuildPlan model before repairs, so malformed task taxonomy/field types are already bounded feedback. The runtime context and nonnegative integer attempt counter are required; there are at most three review attempts. Those execution-safety rules are not app structure to construct. ContextVariablesBridge detaches protected state, so regression fixtures must use the bridge and assert cached state rather than rely on ordinary dict mutation.

Page source compilation now follows declared collection ownership: module output admission constructs missing canonical list/detail actions before dependency publication, and page tasks gain dependencies from their explicit data source pairs. This does not infer ownership from endpoint strings. See the typed page data source contract for construction boundaries and protected-action handling.