Adopting Options.SkillDir across 16 registries (see #142 for the placement half). This one is about correctness of the emitted content, and it is the failure mode the ADR names as the reason the file is gated at all: "a skill that has drifted from the schema is worse than no skill, since it is confidently wrong about the one thing it exists to know." The gate cannot catch this one, because the file is a faithful render of a manifest that is itself wrong.
What it emits
Registry declares camelCase:
var Registry = schema.NewRegistry().WireCase(schema.Camel)
The emitted SKILL.md:
| Capability | Columns |
|---|---|
| Filterable | `id`, `org_id`, `member_id`, `type`, `title`, `read_at`, `created_at`, `read` |
| Sortable | `type`, `read_at`, `created_at`, `read` |
and closes with:
One column has one wire spelling, derived from its name. There is no mapping layer and no per-field override in either direction, so the column names above are also the JSON field names.
The actual wire, from the TypeScript client generated from the same registry in the same run:
orgId: string;
createdAt: string;
sort?: 'createdAt' | '-createdAt' | ...
filter: { orgId?: Cond<string>; createdAt?: Cond<string | Date>; ... }
So the skill lists org_id under Filterable, and states in prose that org_id is therefore the JSON field name. An agent that reads this file and does exactly what it says writes ?org_id=eq.… and gets a 400 — and the capability list it just consulted is what told it that would be accepted. That is worse than the agent having guessed, because guessing from the camelCase model would have been right.
The closing bullet is the sharper half of the bug. The table could be read as "these are columns, translate them yourself"; the prose explicitly forecloses that reading.
Where it comes from
Not the skill renderer — the manifest:
// schema/manifest.go:338
rm.Filterable = append(rm.Filterable, d.Name)
d.Name is the declared column name. The same is true of the example URL builder two dozen lines down:
// schema/manifest.go:393
out = append(out, fmt.Sprintf("GET %s?%s=eq.VALUE", rm.Path, rm.Filterable[0]))
which emits GET /notifications?org_id=eq.VALUE for a camelCase registry — a copy-pasteable request that 400s.
So anything else reading BuildManifest() for a wire contract is wrong in the same way; the skill is just the first consumer where it is visible to a human. renderSkill calls applyOverridesToManifest and then prints, so a fix in the manifest fixes both, and sqlb-schema was correct before only because every prior registry used the default wire case.
Suggested fix
Either the manifest's REST section carries wire spellings (the manifest is a description of the wire, so I would argue Filterable there should always have been the wire name), or it carries both and the skill prints the wire one. If the manifest keeps raw names for other consumers, Manifest needs the registry's WireCase on it so a renderer can do the conversion — right now the skill has no way to know, which is why this cannot be fixed in skill.go alone.
The closing bullet then needs to be conditional: true as written for the default case, and for a non-default WireCase it should say what the mapping is instead. That sentence is doing real work — it is what tells an agent not to go looking for a mapping layer — so it is worth keeping rather than deleting.
Not affected
The four capability lists are right about which columns are exposed; only the spelling is wrong. Values: for enums is right (values are data, not identifiers). The obligations table names columns rather than wire fields, which reads correctly to me — an obligation is about the column.
Adopting
Options.SkillDiracross 16 registries (see #142 for the placement half). This one is about correctness of the emitted content, and it is the failure mode the ADR names as the reason the file is gated at all: "a skill that has drifted from the schema is worse than no skill, since it is confidently wrong about the one thing it exists to know." The gate cannot catch this one, because the file is a faithful render of a manifest that is itself wrong.What it emits
Registry declares camelCase:
The emitted
SKILL.md:and closes with:
The actual wire, from the TypeScript client generated from the same registry in the same run:
So the skill lists
org_idunder Filterable, and states in prose thatorg_idis therefore the JSON field name. An agent that reads this file and does exactly what it says writes?org_id=eq.…and gets a 400 — and the capability list it just consulted is what told it that would be accepted. That is worse than the agent having guessed, because guessing from the camelCase model would have been right.The closing bullet is the sharper half of the bug. The table could be read as "these are columns, translate them yourself"; the prose explicitly forecloses that reading.
Where it comes from
Not the skill renderer — the manifest:
d.Nameis the declared column name. The same is true of the example URL builder two dozen lines down:which emits
GET /notifications?org_id=eq.VALUEfor a camelCase registry — a copy-pasteable request that 400s.So anything else reading
BuildManifest()for a wire contract is wrong in the same way; the skill is just the first consumer where it is visible to a human.renderSkillcallsapplyOverridesToManifestand then prints, so a fix in the manifest fixes both, andsqlb-schemawas correct before only because every prior registry used the default wire case.Suggested fix
Either the manifest's REST section carries wire spellings (the manifest is a description of the wire, so I would argue
Filterablethere should always have been the wire name), or it carries both and the skill prints the wire one. If the manifest keeps raw names for other consumers,Manifestneeds the registry'sWireCaseon it so a renderer can do the conversion — right now the skill has no way to know, which is why this cannot be fixed inskill.goalone.The closing bullet then needs to be conditional: true as written for the default case, and for a non-default
WireCaseit should say what the mapping is instead. That sentence is doing real work — it is what tells an agent not to go looking for a mapping layer — so it is worth keeping rather than deleting.Not affected
The four capability lists are right about which columns are exposed; only the spelling is wrong.
Values:for enums is right (values are data, not identifiers). The obligations table names columns rather than wire fields, which reads correctly to me — an obligation is about the column.