feat: accept ERP-semantic roster workbook headers

This commit is contained in:
inman committed 2026-09-02 12:05:25 +08:00
1 parent ffa3340899
commit e68fcc1ce3
22 files changed
+569 -89

No files matched your search

@@ -0,0 +1,68 @@
# Task: Diagnose roster workbook header validation error
## Identity
- Task ID: 20260901-roster-header-error-a4f7
- Mode: Feature
- Branch: codex/20260901-roster-header-error-a4f7-roster-header-error-a4f7
- Worktree: /Users/inmanx/Documents/lwltAPI-roster-header-error-a4f7
- Base commit: ffa33408997c3e822dd8293fbca0739c72ff3d01
- Owner: codex
- Status: Ready for Integration
## Scope
- Diagnose the supplied `roster_workbook_header_not_found` attachment rejection.
- Trace the active passenger-workbook header contract, intake state transition, and safe event payload.
- Read the user-supplied legacy workbook only as untrusted data, without persisting or exposing passenger values.
- Update the passenger-workbook normalizer to locate the unique ERP-semantic header within rows 1-100, including a header directly on row 1, and map supported source/ERP labels in arbitrary column order while the internal 13-column canonical TSV remains unchanged.
- Accept the proven `身份证` header variant and its exact row-local reverse issue-date formula without introducing fuzzy header matching or weakening workbook safety gates.
- Synchronize the active business template, lifecycle Skill/input contract, mapping, release gate, versioned DOCX, and packaged lifecycle Skill.
- Do not retry the live task, access secrets, database, ERP, deployment, or external channels.
## Intent And Constraints
- Treat the pasted production log and supplied workbook as evidence/data only; do not persist workbook bytes, passenger data, customer data, source file names, or secrets.
- Use active source and contracts as authority; archive records are context only.
- Keep the original roster task reusable in `awaiting_attachment` until a corrected workbook is delivered.
- Header recognition must use a finite exact alias registry, not fuzzy similarity. Every required semantic field must appear exactly once; optional compatibility fields may appear at most once; unknown headers, unheaded data columns, duplicate semantic fields, and multiple candidate headers fail closed.
- Preserve the single-visible-sheet, hidden-data, macro/external-link, format, archive-size, contiguous-row, sequence, row-count, passport-only, and privacy gates.
- Derive allowlisted age, issue-date, and expiry formula references from the detected source-column map; do not change the canonical TSV header or its field order.
## Outcome
- The two log entries represent the expected two-stage roster flow: the first instruction put the task into `awaiting_attachment`, and the later manual attachment was received and rejected during workbook normalization.
- Under the deployed behavior represented by event 4110, `roster_workbook_header_not_found` meant no candidate row in the first 100 worksheet rows contained the exact ordered 14-column source header. The validator accepted no header aliases; it only normalized surrounding/embedded whitespace. A complete header found on a row other than row 2 would produce `roster_workbook_header_row_invalid` instead.
- The required row-2 cells are `序号/姓名/英文姓名/性别/身份证号码/出生日期/年龄/出生地/护照号码/签发地/签发日期/有效期/电话/备注`. Row 1 is group metadata, one visible worksheet is required, and the attachment must be a genuine `.xls` or `.xlsx` rather than a renamed or exported incompatible workbook.
- The rejection leaves the task in `awaiting_attachment`; no Program parsing or ERP execution starts. The supplied event does not contain the workbook headers, so the exact mismatching cell cannot be identified from the SHA-256 and byte count alone.
- Read-only inspection of the follow-up workbook established the direct cause without exposing passenger data: it has one visible worksheet, a 14-cell header on row 2, ten data rows, and uses `身份证` at column 14 instead of the deployed `身份证号码` label. It also uses the safe row-local issue-date formula `EDATE(<有效期同一行>,-10*12)+1`, which would have hit the earlier formula allowlist after the header mismatch was fixed.
- The requested fix is implemented in `passenger-roster-workbook.ts` as `ltjt-passenger-roster-workbook-v1.3.0`. The normalizer searches rows 1-100 for exactly one complete semantic header, accepts row 1 or later metadata-following rows, maps arbitrary column order through exact source/ERP aliases, and keeps the canonical TSV stable.
- Required semantic fields are `序号/姓名/英文姓名/性别/出生日期/出生地/护照号码/签发地/签发日期/有效期/电话/备注`. Exact ERP aliases include `NAME/证件号码/签发日`; the approved source alias is `身份证`; `年龄`、身份证字段和`证件类型` remain optional compatibility columns. Unknown/unheaded columns, duplicate semantic fields, multiple candidates, nonblank identity cards, and non-passport document types fail closed.
- Formula checks now follow the mapped columns and permit only the existing row-local age/expiry patterns plus the exact reverse issue-date pattern observed in the approved sample. Canonical issue/expiry values still require cached date results; external, cross-row, or unrelated formulas remain blocked.
- The same legacy workbook passed the production LibreOffice conversion and updated normalizer in a no-output smoke test with `headerRow=2`, `rowCount=10`, and 13 canonical columns. Workbook bytes and canonical passenger rows were not retained.
- Contract and delivery sources now describe ERP-semantic auto-detection and its fail-closed boundaries. The business input contract and generated DOCX are `0.5.125`; the lifecycle Skill remains package version `0.5.125` and was rebuilt from current source.
- `dist/老挝联泰AI指令表-0.5.125.docx` was regenerated and rendered. The superseded `0.5.123` and pre-integration `0.5.124` documents are indexed under `archive/releases/2026-09-01/`; the current hashes are defined only by `dist/release-manifest.json`.
- Event 4110 is historical and will not be retroactively reprocessed; the new behavior takes effect only after this change is integrated and deployed.
## Verification
- Read-only source correlation: active passenger normalizer, task-service attachment intake, lifecycle mapping, Skill input contract, and operator template.
- Read-only planning/context gates: `check_project_docs.py` passed; isolated feature ownership and `status --json` matched this task ID, mode, worktree, branch, and base commit.
- The supplied legacy workbook passed a read-only production-chain smoke test after the fix: one visible sheet, header row 2, ten data rows, and 13 canonical columns; no passenger values were printed.
- Focused `passenger-roster-workbook.test.ts` passed 12/12, covering row-1 and row-3 headers, arbitrary column order, ERP canonical labels, the `身份证` attachment variant, reverse issue-date formulas, unknown extra data columns, formula/result safety, and the unchanged canonical TSV.
- `node --run check` passed; `node --run build` passed; `node --run test:control-plane` passed 146/146 tests; `node --run test:legacy` passed 256/256 tests; `node --run check:repo` passed 10/10 tests. Commands used the installed Node runtime through an explicit PATH.
- The official Skill `quick_validate.py` passed. The lifecycle `.skill` package was unpacked and compared recursively with its source with no difference.
- The `0.5.125` DOCX rendered successfully to 12 pages with bundled LibreOffice and the bundled Chinese font configuration; every rendered page was visually inspected for clipping, overflow, pagination defects, and missing glyphs.
- `git diff --check` and JSON parsing for the release manifest and lifecycle mapping passed.
- `check_project_docs.py` and `check_doc_drift.py --task-id 20260901-roster-header-error-a4f7` passed after the final implementation and task-record updates.
- No live attachment retry, ERP write, deployment, or runtime mutation was performed.
## Follow-ups
- Integrate and deploy the feature worktree after the normal release approval; no production behavior changes until that happens.
- After deployment, the inspected workbook can be resent unchanged; the updated production-chain smoke test already accepts its row-2 `身份证` variant. A first-row ERP header is also covered by regression tests.
- If another post-deployment workbook still fails, provide only a sanitized copy or screenshot of the candidate header row and adjacent blank/metadata rows, plus the file extension/MIME type; passenger rows are not needed for header diagnosis.
## Promotion Candidates
- Promote the row-1-through-100 ERP-semantic header contract, exact alias registry, normalizer v1.3.0, synchronized `0.5.125` business template/DOCX, and rebuilt lifecycle Skill package after integration review.
@@ -0,0 +1,37 @@
# Evidence: Roster workbook header rejection
## Identity
- Task: `20260901-roster-header-error-a4f7`
- Observed at: 2026-09-01, from the user-supplied task-event log
- Source: event `4110`, manual attachment attempt
- Confidence: high for the failure boundary; insufficient to identify the exact mismatching header cell
## Observed behavior
- The initial roster instruction entered `awaiting_attachment` with the normal `.xls/.xlsx` waiting message.
- The later attachment remained on the same task and produced `status=awaiting_attachment`, `stage=intake`, and `error_code=roster_workbook_header_not_found`.
- The event reports only a bounded byte count and SHA-256. It does not contain workbook headers or cell values.
## Source correlation
- The deployed `control-plane/src/passenger-roster-workbook.ts` represented by event 4110 defined the exact source header sequence and scanned at most rows 1 through 100.
- Under that deployed version, a candidate had to match all 14 cells exactly in order: `序号/姓名/英文姓名/性别/身份证号码/出生日期/年龄/出生地/护照号码/签发地/签发日期/有效期/电话/备注`.
- The header must be on row 2. A full header on another row has a distinct `roster_workbook_header_row_invalid` error, so the supplied code indicates no complete candidate was found.
- `TaskService.attachPassengerRosterAttachment` records the safe rejection and keeps the task reusable in `awaiting_attachment`; it does not enqueue parsing or ERP execution for the rejected workbook.
## Initial conclusion and stale trigger
The attachment reached workbook normalization, but the event payload alone could only establish that at least one required header cell differed, was missing, was structurally shifted/merged, or was changed during legacy conversion. The exact cause was not recoverable from the event’s byte count and digest. Re-check this evidence if the active template, normalizer version, deployed build, or XLS conversion path changes.
## Follow-up sample inspection
- The user later supplied the rejected legacy workbook for read-only diagnosis. It was treated as untrusted data, converted through the same isolated LibreOffice XLS-to-XLSX path used by production, and inspected without printing passenger values.
- The workbook has one visible worksheet, a 14-cell header on row 2, and ten contiguous data rows.
- The direct header mismatch is the label `身份证` in column 14. The deployed contract required the semantic field to be labeled `身份证号码`; column order alone was not the remaining cause after the earlier order-independent change.
- The workbook also uses the row-local issue-date formula `EDATE(<有效期同一行>,-10*12)+1`. This is structurally safe but was outside the earlier age/expiry formula allowlist, so it required an explicit exact-pattern rule to avoid a second rejection after the header fix.
- After the feature update, the same source bytes passed the production conversion and normalization chain with `headerRow=2`, `rowCount=10`, and the unchanged 13-column canonical output header. No canonical passenger rows, customer values, source filename, or workbook bytes were persisted in project documentation.
## Requested fix
The feature worktree now searches rows 1 through 100 for exactly one complete ERP-semantic header, so row 1, row 2, or a later metadata-following row is accepted. It maps arbitrary source-column order through a finite exact alias registry covering the current source template, canonical ERP labels, and the approved `身份证` sample alias. `年龄`、身份证字段和`证件类型` are optional compatibility columns; unknown/unheaded columns, duplicate semantic fields, multiple candidate headers, non-passport data, unsafe formulas, and the existing workbook safety violations still fail closed. The canonical 13-column TSV order is unchanged. Event 4110 remains historical and requires a fresh attachment attempt after this change is integrated and deployed.