Tier 1 YAML-First Authoring Review — Round 2¶
Reviewer stance: fresh review, no prior context assumed
Document reviewed: docs/designs/authoring/tier1-yaml-first-authoring.md
Codebase verified against: /home/qworg/Development/MAID/packages/
Verdict¶
NOT SHIP
The document is improved in a few areas (@ref: syntax, World API caveats, and the explicit note that reconciliation is not implemented yet), but it still contains multiple serious mismatches with the actual MAID codebase. The biggest remaining problems are that the doc presents a non-existent DataDrivenContentPack implementation as if it were real, and its author-friendly YAML examples still do not match the actual loader path that exists today.
Quick status against requested checks¶
| Check | Result | Notes |
|---|---|---|
1. DataDrivenContentPack.on_load() constructs a real LoaderContext |
FAIL | Snippet is not implementable as written; the class/module do not exist in repo. |
2. All YAML examples use _meta.schema |
FAIL | Two _schema examples remain. |
| 3. Assembly Layer vs component-centric examples | FAIL | Doc describes a first-class Assembly Layer, but real pipeline still consumes component-centric components: payloads. |
| 4. Entity types scoped to room/npc/item/template for v1 | FAIL | Doc still treats monster/quest/zone/spell/skill/shop as quasi-standard in several places. |
| 5. Hooks actually wired | FAIL | Documented hooks are not implemented in code. |
| 6. Component field names match real models | PARTIAL FAIL | Some fields match; several examples still do not. |
7. @ref: syntax matches resolver |
PASS / PARTIAL | Syntax matches; dependency-enforcement claim does not. |
| 8. Rule suppression scoped to what exists | FAIL | Global skip_rules exists; _suppress is still documented like a real YAML feature. |
| 9. Persistence reconciliation honestly described | PASS / PARTIAL | Caveat that reconciliation is not implemented is honest; supporting component definitions are still inaccurate. |
| 10. World API calls use real methods | PASS | The examples I checked use real methods and correctly warn that name/tag helpers do not exist. |
| 11. Source of Truth section present and coherent | PARTIAL PASS | Present and directionally coherent, but concrete component definitions are still inaccurate. |
Issues¶
BLOCKER — §4.2, §4.3, §4.4, §12.4, §13.2: DataDrivenContentPack is presented as real implementation, but no such implementation exists¶
What's wrong
The document presents DataDrivenContentPack as if it is an implemented base class with working on_load() behavior, working hooks, and loader integration. It also claims ContentPackLoader can auto-generate a DataDrivenContentPack when a pack has manifest.toml but no pack.py.
That is not true in the codebase:
- there is no
maid_engine.plugins.data_drivenmodule, - there is no
DataDrivenContentPackclass, ContentPackLoader._load_from_directory()still expectspack.pyand only loads a realContentPackimplementation.
This is not a minor wording problem: the document describes current mechanics that simply do not exist.
Section references
- Doc:
tier1-yaml-first-authoring.md:280-579 - Doc:
tier1-yaml-first-authoring.md:807-817 - Code:
packages/maid-engine/src/maid_engine/plugins/protocol.py:30-220 - Code:
packages/maid-engine/src/maid_engine/plugins/loader.py:229-255
Suggested fix
Pick one:
- Implement it for real (
maid_engine/plugins/data_driven.py, loader support, tests), then keep the current tone. - Recast the whole section as proposed design, not current behavior. Use future tense everywhere and remove statements that imply the repo already has this feature.
- Remove the zero-Python auto-generation example until
ContentPackLoaderactually supports it.
BLOCKER — §5, §6.2, §6.3, §6.4: the documented “Assembly Layer” is not wired into the real pipeline, so author-friendly YAML examples would not load as described¶
What's wrong
The doc says PreparePhase has an Assembly Layer that converts top-level author-friendly fields like name, short_desc, health, npc, extended, location, etc. into the component-centric components: structure.
The actual code does not do that:
PreparePhasestill readsexpanded.get("components", {})directly.InstantiatePhaseinstantiates whatever is already indefinition.components.EntityAssemblerexists, but it is not invoked byPreparePhaseorInstantiatePhase.EntityTypeConfigdoes not carry the documentedassembly_rulehook.
As a result, the doc's primary author-facing YAML examples are not backed by the current loader path.
Section references
- Doc:
tier1-yaml-first-authoring.md:824-1023 - Code:
packages/maid-engine/src/maid_engine/loader/phases/prepare.py:139-172 - Code:
packages/maid-engine/src/maid_engine/loader/phases/instantiate.py:33-69 - Code:
packages/maid-engine/src/maid_engine/loader/assembler.py:15-80 - Code:
packages/maid-engine/src/maid_engine/loader/models.py:139-145
Suggested fix
Either:
- wire a real assembly step into
PreparePhaseand/orInstantiatePhase, including explicit field mapping support and tests, or - rewrite the examples to use the actual current shape (
components:) and move the Assembly Layer to a future-work section.
HIGH — §4.2: the “real LoaderContext” fix is still wrong as written (LoaderContext is slotted; _pack_extras assignment would fail)¶
What's wrong
The doc explicitly claims the revised on_load() now builds a real LoaderContext, then shows:
But LoaderContext is declared as @dataclass(slots=True). Adding arbitrary attributes not declared in the dataclass is not allowed. That assignment would raise AttributeError.
So even the "fixed" snippet is not valid against the real LoaderContext type.
Section references
- Doc:
tier1-yaml-first-authoring.md:394-400,445-477, especially475 - Code:
packages/maid-engine/src/maid_engine/loader/models.py:156-174
Suggested fix
Add an explicit field to LoaderContext (e.g. pack_extras: dict[str, Any] = field(default_factory=dict)) or stop claiming ad-hoc attributes can be attached to it.
HIGH — §4.2: built-in semantic rules would be double-registered in the documented DataDrivenContentPack implementation¶
What's wrong
The doc's _build_loader_context() sets:
all_rules = list(BUILTIN_RULES) + self.custom_rules()context.semantic_rules = all_rules
But PreparePhase already does:
That means MAID-S001 and MAID-S005 would run twice, producing duplicate diagnostics.
Section references
- Doc:
tier1-yaml-first-authoring.md:461-472 - Code:
packages/maid-engine/src/maid_engine/loader/phases/prepare.py:41-43 - Code:
packages/maid-engine/src/maid_engine/loader/rules/builtin.py:10-66
Suggested fix
Only put custom rules into context.semantic_rules, or change PreparePhase so it does not prepend built-ins again.
HIGH — §6.1, §6.5-§6.10, §7.3: v1 scope is still inconsistent; the doc keeps treating non-core entity types as if they are standard¶
What's wrong
The doc correctly says the built-in schemas are the four core types: room, npc, item, template. But later sections still present monster/quest/zone/spell/skill/shop as if they are part of the standard schema surface, including a CLI schema export example that outputs ten schemas.
The actual code only ships four standard entity type configs.
This leaves the reader with two conflicting stories: "v1 is four core types" and "the engine exports many more standard types".
Section references
- Doc:
tier1-yaml-first-authoring.md:1044-1055 - Doc:
tier1-yaml-first-authoring.md:2181-2204 - Doc:
tier1-yaml-first-authoring.md:2468-2510,2646-2682 - Code:
packages/maid-engine/src/maid_engine/loader/entity_types.py:7-44
Suggested fix
Split the document clearly into:
- engine-provided v1 core: room / npc / item / template
- pack-provided examples: monster / quest / zone / spell / skill / shop
Do not show non-core schemas in "out of the box" export examples unless that export is explicitly scoped to a pack that registered them.
HIGH — §6.13 and §7.3: _schema examples still remain¶
What's wrong
The doc now says schema lives under _meta.schema, and PreparePhase._resolve_schema() indeed reads _meta.schema. But two examples still use _schema:
- migration adapter writes
data["_schema"] = "maid:room:v2" - JSON Schema export example exposes a top-level
"_schema"property
That directly contradicts the doc's own corrected convention.
Section references
- Doc:
tier1-yaml-first-authoring.md:1031-1041 - Doc:
tier1-yaml-first-authoring.md:2131-2136 - Doc:
tier1-yaml-first-authoring.md:2215-2218 - Code:
packages/maid-engine/src/maid_engine/loader/phases/prepare.py:224-237
Suggested fix
Replace both remaining _schema examples with _meta.schema, or explicitly label them as legacy-compat examples if that is the intent.
HIGH — §6.2, §6.3, Appendix A: several author-facing field shapes still do not match the real component models¶
What's wrong
A spot-check found some fields that are correct (DescriptionComponent.name, HealthComponent.current, HealthComponent.maximum, NPCComponent.behavior_type, DialogueComponent.personality), but multiple examples are still wrong in ways that matter to authors:
- Extended room details use
condition:singular, butRoomDetailusesconditions:. - Autonomy schedule is shown as a plain list of
{time, action, target}entries, butScheduleComponentactually expectsblockswithstart_hour,end_hour,activity,location,priority, and optionalconditions. - Needs are shown as flat scalars (
hunger: 0.3, etc.), butNeedsComponentexpects aneedsmapping ofNeedCategory -> Needobjects. - Goals are shown as a simple list under
goals, butGoalsComponentactually storesactive_goals,completed_goals, andfailed_goals, withGoalobjects using fields likecategory,description,priority, etc. - Combat examples use fractional
critical_chancevalues like0.05/0.1, but the realCombatComponent.critical_chanceis anint.
These are not cosmetic mismatches; they materially affect whether examples correspond to real model shapes.
Section references
- Doc:
tier1-yaml-first-authoring.md:1198-1202 - Doc:
tier1-yaml-first-authoring.md:1343-1389 - Doc:
tier1-yaml-first-authoring.md:1579-1585 - Doc:
tier1-yaml-first-authoring.md:4013-4024 - Code:
packages/maid-stdlib/src/maid_stdlib/components/extended_room.py:473-488, 742-752 - Code:
packages/maid-stdlib/src/maid_stdlib/models/npc/autonomy.py:131-137, 265-279, 322-329 - Code:
packages/maid-stdlib/src/maid_stdlib/components/combat.py:13-28
Suggested fix
Either:
- make these examples match the actual component models, or
- explicitly mark them as future assembly-layer syntax and do not present them as if they already correspond to real component fields.
HIGH — §8.4 and Appendix A: rule suppression is still overstated relative to what exists¶
What's wrong
The code currently supports a global LoaderConfig.skip_rules set. It does not support per-entity _suppress YAML directives or _meta.yaml per-file rule toggles.
The doc does mention those as future enhancements in one note, but Appendix A still documents _suppress as a real top-level field, which makes the current capability boundary unclear.
There is a second problem: the examples use rule IDs like MAID-K001 and MAID-C001 as if they already exist in the current codebase, but built-ins today are only MAID-S001 and MAID-S005.
Section references
- Doc:
tier1-yaml-first-authoring.md:2609-2633 - Doc:
tier1-yaml-first-authoring.md:3997 - Code:
packages/maid-engine/src/maid_engine/loader/models.py:133-145 - Code:
packages/maid-engine/src/maid_engine/loader/phases/prepare.py:197-199 - Code:
packages/maid-engine/src/maid_engine/loader/rules/builtin.py:10-66
Suggested fix
- Move
_suppressout of the current top-level field reference until it exists. - Use currently real rule IDs in current-capability examples.
- Put future rule configuration in a clearly marked future-work subsection.
MEDIUM — §3.2 and §3.4: Source-of-Truth section is present, but the concrete component definitions are still inaccurate¶
What's wrong
The section is directionally better now: it correctly says YAML is canonical and explicitly acknowledges that real reconciliation is not implemented yet. That part is honest.
However, the concrete component definitions shown in the section still drift from the code:
InstanceStateComponentis shown withdirty_components, but the real model usesmodified_components, pluslast_modified_atandmodified_by.DataProvenanceComponentis shown as a simple dataclass with four fields, but the real model also includesdefinition_idanddefinition_version.
Since this section is meant to define the ownership/reconciliation model, its concrete component shapes should be exact.
Section references
- Doc:
tier1-yaml-first-authoring.md:157-177 - Code:
packages/maid-engine/src/maid_engine/loader/models.py:209-236 - Code:
packages/maid-engine/src/maid_engine/loader/phases/instantiate.py:60-68
Suggested fix
Update the code snippets to match the actual models exactly, or explicitly label them as simplified pseudocode.
MEDIUM — §12.7: cross-pack @ref: syntax is correct, but the dependency-enforcement claim is not¶
What's wrong
The syntax examples for @ref:type/id, @ref:pack:type/id, and @ref:uuid:... match the resolver.
But the doc also says: "The referenced pack must be declared as a dependency in manifest.toml." The resolver does not enforce that. It simply looks up names in the shared ReferenceRegistry. If a pack's symbols are registered, they are resolvable regardless of whether the manifest declared that dependency.
Section references
- Doc:
tier1-yaml-first-authoring.md:3559-3585 - Code:
packages/maid-engine/src/maid_engine/loader/phases/resolve_refs.py:194-217 - Code:
packages/maid-engine/src/maid_engine/loader/resolver.py:50-71
Suggested fix
Either implement dependency-aware resolution checks, or weaken the wording to say dependency declaration is a recommended packaging rule / load-order expectation, not a current resolver guarantee.
MEDIUM — §6.2 and Appendix A: grid is still described as a standard room top-level field, but the core room config does not allow it¶
What's wrong
The room examples and Appendix A still advertise grid as a normal room top-level field. But the real ROOM_CONFIG.allowed_top_level_fields only allows exits and zone.
This is also internally inconsistent with the doc's own schema-show example, which lists allowed room top-level fields as exits, zone.
Section references
- Doc:
tier1-yaml-first-authoring.md:1151-1154 - Doc:
tier1-yaml-first-authoring.md:2337-2340 - Doc:
tier1-yaml-first-authoring.md:4004-4007 - Code:
packages/maid-engine/src/maid_engine/loader/entity_types.py:7-13
Suggested fix
Either add grid to the real room config (and document how it is assembled/validated), or remove it from the v1 core room schema examples and mark it as pack- or world-system-specific extension data.
What looks genuinely fixed / accurate¶
@ref: syntax itself¶
The syntax description aligns with the real resolver:
@ref:type/id@ref:pack:type/id@ref:uuid:<uuid>
Evidence
- Doc:
tier1-yaml-first-authoring.md:4040-4044 - Code:
packages/maid-engine/src/maid_engine/loader/phases/resolve_refs.py:150-217
World API examples¶
The document now correctly says that World does not provide get_entity_by_name() or get_entities_by_tag(), and the sample code uses world.get_all_entities() instead. That matches the real World API.
Evidence
- Doc:
tier1-yaml-first-authoring.md:3487-3491 - Code:
packages/maid-engine/src/maid_engine/core/world.py:329-332, 357-457
Reconciliation caveat¶
The document now openly states that InstantiatePhase does not perform reconciliation and that the described conflict-resolution behavior is a prerequisite/future step. That is honest and aligned with the current implementation, which always takes the NEW path.
Evidence
- Doc:
tier1-yaml-first-authoring.md:213-218 - Code:
packages/maid-engine/src/maid_engine/loader/phases/instantiate.py:33-39
Bottom line¶
This should not ship yet.
The review-cycle fixes were only partial. Some prior problem areas improved, but the document still over-claims current implementation status in multiple places, and several key author-facing examples still do not line up with the real code. The largest remaining gap is unchanged: the doc is still describing a YAML-first authoring system that the current loader path does not actually implement.