meta { name: POST skill progression grant type: http seq: 2 } docs { NEO-38: single skill XP grant + allowlist + level-up metadata; see server README. NEO-40: same contract; server adds comment-only telemetry hook markers (no request/response change). NEO-47: on level-up, server re-evaluates mastery perk unlocks internally (no JSON change until NEO-48 perk-state HTTP). Success responses include reasonCode: null (stable since NEO-38); perk-state POST omits reasonCode on success instead. E2.M2 tracking row in docs/decomposition/modules/documentation_and_implementation_alignment.md lists NEO-40 landed with plan + manual QA links. } post { url: {{baseUrl}}/game/players/dev-local-1/skill-progression body: json auth: none } body:json { { "schemaVersion": 1, "skillId": "salvage", "amount": 10, "sourceKind": "activity" } } tests { test("grant returns 200 with granted true and progression", function () { expect(res.getStatus()).to.equal(200); const body = res.getBody(); expect(body.schemaVersion).to.equal(1); expect(body.granted).to.equal(true); expect(body.reasonCode).to.equal(null); expect(body.progression).to.be.an("object"); expect(body.progression.schemaVersion).to.equal(1); expect(body.progression.playerId).to.equal("dev-local-1"); expect(body.levelUps).to.be.an("array"); const salvage = body.progression.skills.find((x) => x.id === "salvage"); expect(salvage).to.be.an("object"); expect(salvage.xp).to.be.a("number"); }); }