Really useful write-up. The "a resume is structured data, not a document" reframe matches what we saw too. Two things that bit us when we tried something similar on the interview side:
Gap analysis needs a confidence field, not just a yes/no. A JD that says "experience with distributed systems" can be matched by a Kafka project, a sharded Postgres migration, or nothing at all, and a hard boolean pushed the rewrite chain to either overclaim or drop real experience. Scoring each match (strong / partial / inferred) and only allowing rewrites on strong matches cut fabricated bullets a lot.
Keep the canonical resume model reusable downstream. Once the CRDM exists, the same typed object is great for generating STAR stories and likely interview questions per role, and since every claim traces back to a parsed field, the candidate never ends up defending a bullet in an interview that they didn't actually write.
Curious how you handle multi-role JDs (e.g. "backend or full-stack") in the CJDM, do you split them into two models or merge requirements?
(Disclosure: I work on Linkjob AI copilot, which uses the resume + JD pairing for live interview answers, so this architecture question is close to home.)