Skip to content

fix: join FR-keyed story-map rows when user-stories is skipped - #1176

Merged
apackeer merged 2 commits into
awslabs:mainfrom
vnlebaoduy:fix/traceability-fr-story-map
Sep 19, 2026
Merged

apackeer merged 2 commits into
awslabs:mainfrom
vnlebaoduy:fix/traceability-fr-story-map

Conversation

@vnlebaoduy

Copy link
Copy Markdown
Contributor

Summary

When a scope skips user-stories, the traceability sensor at units-generation resolves its source ids correctly - resolveUpstream() falls back from US ids in stories.md to FR ids in requirements.md - but then resolves unit assignments through storyAssignments(), which parsed unit-of-work-story-map.md with the US pattern only (core/tools/aidlc-sensor-traceability.ts:233). A map whose rows carry FR1 | U1 | u1-auth therefore yields zero assignments, and every FR id is reported as a gap even when each is mapped, so the sensor can never pass for that scope shape.

Closes #1117.

Changes

  • core/tools/aidlc-sensor-traceability.ts - storyAssignments() takes the id pattern to join on. The units-generation branch passes the same pattern it used for the source (US when stories.md exists, otherwise FR), so the two halves of the join can no longer disagree. The two per-unit call sites (functional-design, code-generation) are already inside existsSync(stories) guards and pass US explicitly; their behaviour is unchanged.
  • tests/unit/t281-sensor-traceability.test.ts - new case "units-generation joins FR rows when user-stories is skipped": requirements only, units seeded, an FR-keyed story map, and an FR-keyed traceability.json pass; a row that maps FR2 to the wrong unit is still refused through invalid_targets (FR2: target "u2-profile" is not mapped in unit-of-work-story-map.md).

No version bump, badge, or CHANGELOG entry, per the Release Metadata Policy in AGENTS.md.

User experience

Before: in any scope without user-stories (the fallback the units-generation stage prose describes: "otherwise enumerate every FR"), the sensor reported every FR as a phantom gap at units-generation and the stage recorded SENSOR_FAILED regardless of the artifact's content.

After: FR-keyed rows join to units the same way US-keyed rows do; the sensor passes on complete coverage and still names a wrong-unit target.

Checklist

  • I have reviewed the contributing guidelines
  • I have performed a self-review of this change
  • Changes have been tested
  • Changes are documented
  • If this change adds an input to any fingerprint, epoch, or receipt identity, the description names the human-visible change it detects

Test plan

On 572c8265 (main):

bun test tests/unit/t281-sensor-traceability.test.ts                      # 14 pass
bun tests/run-tests.ts --unit --filter "<the 31 unit files mentioning traceability>"   # 31 / 31 PASS
bun tests/gen-coverage-registry.ts --check                                # fresh, guards green, ratchet held
bun run lint && bun run typecheck
bun scripts/package.ts --check                                            # deterministic, 7 harnesses

The new case fails without the sensor change: git stash push -- core/ && bun test tests/unit/t281-sensor-traceability.test.ts reports 1 failure (pass is false because every FR is a gap).

Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.

At units-generation the traceability sensor resolves source ids with a
US-to-FR fallback when stories.md is absent, but storyAssignments()
parsed unit-of-work-story-map.md with the US pattern only. A map whose
rows carry FR ids yielded no assignments, so every FR was reported as a
gap and the sensor could never pass in a scope without user-stories.

storyAssignments() now takes the id pattern to join on, and the
units-generation branch passes the same pattern it used for the source.
The per-unit call sites already sit behind an existsSync(stories) guard
and pass US explicitly, so their behaviour is unchanged.

t281 adds the FR-only case: complete FR coverage passes, and a row that
maps an FR to the wrong unit is still refused through invalid_targets.

Closes awslabs#1117
@vnlebaoduy
vnlebaoduy force-pushed the fix/traceability-fr-story-map branch from 5686315 to 51bdd2c Compare September 15, 2026 15:53
@vnlebaoduy

Copy link
Copy Markdown
Contributor Author

Rebased onto main @ 891669aa (after #1179 landed). The one conflict was the guarded storyAssignments call in the code-generation branch; kept both changes (unit && existsSync(storyMap) from #1179, the explicit ID_PATTERNS.US from this PR). t281 now 16 / 16, matching the combined result noted in the #1179 review; lint, typecheck, coverage registry, and packaging determinism green on the rebased head 51bdd2cf.

…skipped

Issue awslabs#1117's reproduction traces NFR1–NFR5 as OK rows next to the FRs. With
the join restricted to the FR pattern, storyAssignments() dropped the NFR
rows and verifyTargets() reported each of them as "not mapped in
unit-of-work-story-map.md" even though the row exists, so the sensor still
failed on the reporter's artifact.

storyAssignments() now takes the pattern list and the units-generation
branch joins on [FR, NFR] when stories.md is absent. The required upstream
set stays FR-only ("otherwise enumerate every FR"); NFR coverage is optional
but validates against the map when present. The regression case covers all
three: FR-only coverage passes with NFRs in requirements.md, an NFR row
joins, and a wrong-unit NFR row is refused.

@apackeer apackeer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this fix, and for the precise root-cause write-up — threading the source fallback through storyAssignments() so the two halves of the join can't disagree is exactly the right shape, and the regression case was well targeted.

I reviewed the current head 02a393f1 against main at 0e54914f. The change restores the FR fallback documented in units-generation.md:129-132, and Leandro's #1179 review already scoped this PR as the complementary follow-up.

During review I found that #1117's own reproduction still failed at 51bdd2cf: the reporter traces NFR1–NFR5 OK alongside the FRs with matching story-map rows, but the FR-only join dropped those rows, so verifyTargets reported each as not mapped in unit-of-work-story-map.md — five false invalid_targets, pass=false. I pushed 02a393f1 on top of your commit: storyAssignments() takes the pattern list and the no-stories join accepts [FR, NFR], while the required upstream set stays FR-only so NFR coverage remains optional. The t281 case now also covers FR-only coverage passing with NFRs in requirements.md, an NFR row joining, and a wrong-unit NFR row being refused. Your sourcePattern became hasStories, which also removes the repeated existsSync.

Validation on 02a393f1: t281 16/16 (each half fails on the prior sensor as claimed), lint, typecheck, coverage registry, packaging determinism across seven harnesses; CI, Markdownlint, and Security Scanners are green on this head.

Approving. One non-blocking follow-up for a separate PR: units-generation.md:124 still says story-map rows are keyed USx.y — worth adding "or FR/NFR when stories.md is not produced" so authors in a no-stories scope know to write those rows.

@apackeer
apackeer added this pull request to the merge queue Sep 19, 2026
Merged via the queue into awslabs:main with commit 7c154cf Sep 19, 2026
23 checks passed
wowzoo pushed a commit to wowzoo/aidlc-workflows that referenced this pull request Sep 20, 2026
…ess-v3

Base moved to 9290c03 (seven commits since 488ab10: awslabs#1176, awslabs#1254, awslabs#1150, awslabs#1191,
awslabs#1253, awslabs#1230, awslabs#1236), which left the PR DIRTY. Five conflicts, all of them traceable
to one upstream commit - 261083c (awslabs#1150, review Construction at verified Unit and
batch checkpoints) - and each resolved from the merge stages rather than by taking a
side:

- core/aidlc-common/protocols/stage-protocol-construction.md - upstream rewrote the
  per-harness Construction routing for `### Kiro CLI` and `### Kiro IDE` with
  byte-identical bodies (sha c768b1d0, 3046 bytes each). This branch collapses the
  two sections into a single `### Kiro`, and the surviving body merged cleanly
  against upstream's rewrite, so the unified section already carries it. The
  duplicated CLI block and the `### Kiro IDE` heading are dropped; no upstream
  change is lost.
- core/aidlc-common/protocols/stage-protocol-swarm.md - the same shape, and
  upstream's two bodies are byte-identical here as well (sha 8223e9d1, 6091 bytes
  each).
- harness/kiro/skills/aidlc/SKILL.md - the `run-stage` row was changed only upstream,
  so upstream's row is taken; the `ask` row was changed only on this branch, so this
  branch's row is kept. Row order is preserved.
- harness/kiro-ide/skills/aidlc/SKILL.md (modify/delete) - stays deleted. The
  upstream change it carried is the `legacy-plan-approval-recovery` ask clause, which
  is already present in the unified harness/kiro/skills/aidlc/SKILL.md on this branch
  and absent from upstream's kiro copy, so the deletion drops a duplicate rather than
  a fix.
- tests/.coverage-registry.json - regenerated with `bun tests/gen-coverage-registry.ts`
  instead of being hand-merged, since it is generated output. `--check` reports
  "OK (fresh, guards green, ratchet held)". tests/.coverage-ratchet.json is the
  generator's companion output.

Verified locally with `bun run typecheck` only (0 errors across all three projects,
after `bun scripts/package.ts` generates the gitignored dist trees the registry
generator reads). The test battery is deliberately not run here - CI is the gate for
this PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: traceability sensor reports every FR id as a phantom gap when user-stories is skipped (storyAssignments US-only parse)

2 participants