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_coverageskips 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_contracttask merely because entities or adata_contractexist. They also do not independently mandate a client task merely because a managed provider exists. Required managed template outputs already materialize independently throughassemble_app_tasks._apply_managed_capability_templates→resolve_managed_capability_templates, using canonicalresolve_declared_pack_output_paths. Those outputs are C/already constructed by assembly; they do not prove a missing task requirement. app_build_planremains 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_packsformerly iterated a union of protected selectedcapability_packsandavailable_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_operationand_plural_entity_slug, which inventedlist_<plural first entity>. Approvedowned_mutationsnow 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 knownmozaikspaycategory), not source labels such asmanaged_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_targetis declared in the typed model but not independently validated inapp_build_plan; valid plans useAppGenerator. 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
AppBuildPlanmodel 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.jsonpreserves the model payload and projects the persisted approved context with local metadata normalized. The actual approved design includes an app-ownedauthsurface; 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.