feat: vault contract v1.2 — experience skill_links (per-link proficiency)#48
Conversation
…cy annotations)
Additive over 1.1; manifest schema_version stays 1. Adds an optional
`skill_links` array to the experience entity only — `{ skill_id, proficiency? }`
objects that annotate a per-skill proficiency (1-5) for skills already listed
in `skill_ids`. `skill_ids` remains authoritative for membership: links whose
skill_id is not in skill_ids carry no membership effect (dropped by the store
API). Empty `skill_links` is omitted from frontmatter so 1.1 vaults round-trip
byte-identically. Stories are unchanged.
- docs/schema/vault-v1/vault-v1.schema.json: add `$defs/skillLink`
(additionalProperties:false, reuses shared proficiency def), wire
`skill_links` into experienceFrontmatter, append 1.2 note to `$comment`.
- docs/schema/vault-v1/README.md: revision-history entry + rule 2 prose,
bump header to revision 1.2.
- schema.py: new `SkillLink` model; optional `skill_links` on ExperienceSchema.
- vault_io.py: `skill_links` in EXPERIENCE_FRONTMATTER_KEYS; `_OMIT_WHEN_EMPTY`
so empty collections are dropped from frontmatter.
- proposals.py: `skill_links` in `_EXPERIENCE_KEYS` only.
- vault.py / cli.py: add_experience accepts & filters skill_links; new
repeatable `--skill-link '<uuid>:<1-5>'` CLI flag + JSON-batch support.
- tests: schema, CRUD round-trip + empty-omit, proposal accept (exp) / reject
(story); relax the frontmatter-contract test for omit-when-empty keys.
https://claude.ai/code/session_01PsAQUnoLH94f2cbK2dSpox
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b21cacd36c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| """ | ||
|
|
||
| skill_id: UUID | ||
| proficiency: int | None = Field(default=None, ge=1, le=5) |
There was a problem hiding this comment.
Omit unset link proficiency from serialized vaults
When a link is created with no proficiency (accepted by this model and by bare --skill-link parsing), Pydantic keeps proficiency=None; the existing frontmatter writer uses model_dump(mode="json"), so the vault is written with proficiency: null. The v1.2 JSON Schema only permits an integer when proficiency is present, so a load/save round-trip of an otherwise valid optional-proficiency link produces a vault that schema/cloud validation can reject. Omit None fields inside skill_links before writing, or make the contract explicitly nullable.
Useful? React with 👍 / 👎.
| description: str = "" | ||
| accomplishments: list[str] = Field(default_factory=list) | ||
| skill_ids: list[UUID] = Field(default_factory=list) | ||
| skill_links: list[SkillLink] = Field(default_factory=list) |
There was a problem hiding this comment.
Enforce skill_links membership in the model
Since ExperienceSchema is used directly by read_vault_tree and proposal approval, this field accepts and later re-emits skill_links whose skill_id is not in skill_ids; the only filtering is in VaultStore.add_experience. If a hand-edited/synced vault or an add/update_experience proposal carries a stale link, the model preserves a proficiency annotation for a skill that is not exercised in the role, even though v1.2 says such entries are ignored and skill_ids is authoritative. Normalize or drop those links during ExperienceSchema validation so every load/apply path has the same behavior.
Useful? React with 👍 / 👎.
Summary
Vault contract revision 1.2 — adds optional per-link proficiency to experiences (the Local + contract-schema side; Cloud side is traitprint-cloud's paired PR). Additive:
schema_versionstays1, vaults written against 1.0/1.1 remain valid and round-trip byte-identical.New optional
skill_links: [{ skill_id, proficiency? 1–5 }]on experiences only (stories unchanged — proficiency belongs to a multi-year role, not a single STAR anecdote, and it avoids a projection migration on the Cloud side).skill_idsstays authoritative for membership;skill_linksonly annotates a skill already inskill_ids(stray entries are dropped). Emptyskill_linksis omitted from frontmatter so 1.1 vaults stay byte-identical.Changes
docs/schema/vault-v1/vault-v1.schema.json:$defs/skillLink,experienceFrontmatter.skill_links,$commentbumped to 1.2; manifestschema_versionstays1. Story$defsuntouched.schema.py:SkillLinkmodel +ExperienceSchema.skill_links(default empty);StorySchemauntouched.vault_io.py:skill_linksinEXPERIENCE_FRONTMATTER_KEYS+ omit-when-empty.proposals.py:skill_linksin_EXPERIENCE_KEYSonly.vault.py/cli.py:add_experienceacceptsskill_links(filters stray skill_ids); repeatable--skill-link '<uuid>:<1-5>'flag + JSON-batch support.Testing
pytest: 622 passed / 3 skipped (exit 0)ruff check: clean (exit 0)mypy: 1 error atcli.py:38(click.ParamType[UUID]) — pre-existing, confirmed byte-identical on a clean origin/main worktree; not introduced here.skill_linksrender parses correctly on Cloud's frontmatter parser, and Cloud's render parses back through PyYAML + the schema — round-trips in both directions with proficiency preserved.Pairs with traitprint-cloud's v1.2 PR; they share one authoritative contract spec.
https://claude.ai/code/session_01PsAQUnoLH94f2cbK2dSpox
Generated by Claude Code