@besales/ops-framework 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/bin/initiative.mjs +78 -0
- package/bin/initiative.test.mjs +96 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.16
|
|
4
|
+
|
|
5
|
+
- Added a schema compatibility readiness gate that blocks planning when a closed/minimal schema requirement does not support dependent approved requirements such as speaker resolution, aliases and meeting track classification.
|
|
6
|
+
- Added a schema attribution/access warning when sources mention `created_by`, `app.current_user_id`, `owner_id`, `entity_history` or deferred enforcement but the schema requirement omits them.
|
|
7
|
+
|
|
3
8
|
## 0.1.15
|
|
4
9
|
|
|
5
10
|
- Strengthened the initiative track-scope/storage synthesis gate to detect wide meeting tracks routed to commitments/decisions/risks via `related_person_id` when that storage is deferred.
|
package/bin/initiative.mjs
CHANGED
|
@@ -1035,6 +1035,8 @@ function buildSynthesisReadinessGates({ requirements, sourceDocs }) {
|
|
|
1035
1035
|
});
|
|
1036
1036
|
addScopeStorageConflictGate({ gates, sourceText, requirementText });
|
|
1037
1037
|
addApplyContractGate({ gates, sourceText, requirementText });
|
|
1038
|
+
addSchemaCompatibilityGate({ gates, sourceText, requirements });
|
|
1039
|
+
addSchemaAttributionWarningGate({ gates, sourceText, requirements });
|
|
1038
1040
|
addSequencingGate({ gates, sourceText, requirementText });
|
|
1039
1041
|
addStrongerSourceRefGate({ gates, sourceDocs, requirements });
|
|
1040
1042
|
return gates;
|
|
@@ -1100,6 +1102,82 @@ function addApplyContractGate({ gates, sourceText, requirementText }) {
|
|
|
1100
1102
|
}
|
|
1101
1103
|
}
|
|
1102
1104
|
|
|
1105
|
+
function addSchemaCompatibilityGate({ gates, sourceText, requirements }) {
|
|
1106
|
+
const schemaReq = findSchemaRequirement(requirements);
|
|
1107
|
+
if (!schemaReq) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
const schemaText = requirementFullText(schemaReq);
|
|
1111
|
+
const allRequirementText = requirements.map((req) => requirementFullText(req)).join('\n');
|
|
1112
|
+
const schemaLooksClosed = /(includes?\s+only|only\s+includes?|schema\s+includes\s+only|только|исключительно)/i.test(schemaText);
|
|
1113
|
+
const hasSpeakerResolutionRequirement = /(speaker[_ -]?resolution|speaker identity|raw speaker|raw_speaker|person_aliases|aliases?|alias-хранилище)/i.test(allRequirementText);
|
|
1114
|
+
const schemaSupportsSpeakerResolution = /(person_aliases|raw_speaker_labels?|speaker_resolution_status|speaker alias|speaker label|aliases?)/i.test(schemaText);
|
|
1115
|
+
const hasTrackClassificationRequirement = /(meeting_track|track classification|classif(?:y|ication).{0,80}track|классификац.{0,80}трек)/i.test(`${allRequirementText}\n${sourceText}`);
|
|
1116
|
+
const schemaSupportsTrackClassification = /meeting_track/i.test(schemaText);
|
|
1117
|
+
const sourceMentionsMeetingOperationalFields = /(series_id|asr_quality)/i.test(sourceText);
|
|
1118
|
+
const schemaSupportsMeetingOperationalFields = !sourceMentionsMeetingOperationalFields || /(series_id|asr_quality)/i.test(schemaText);
|
|
1119
|
+
const missing = [];
|
|
1120
|
+
if (hasSpeakerResolutionRequirement && !schemaSupportsSpeakerResolution) {
|
|
1121
|
+
missing.push('speaker resolution storage (`person_aliases` or `raw_speaker_labels` + `speaker_resolution_status`)');
|
|
1122
|
+
}
|
|
1123
|
+
if (hasTrackClassificationRequirement && !schemaSupportsTrackClassification) {
|
|
1124
|
+
missing.push('`meeting_track` on meetings');
|
|
1125
|
+
}
|
|
1126
|
+
if (!schemaSupportsMeetingOperationalFields) {
|
|
1127
|
+
missing.push('meeting operational fields mentioned by sources, such as `series_id` and `asr_quality`');
|
|
1128
|
+
}
|
|
1129
|
+
if (missing.length && schemaLooksClosed) {
|
|
1130
|
+
gates.push({
|
|
1131
|
+
id: 'schema-supports-derived-requirements',
|
|
1132
|
+
severity: 'critical',
|
|
1133
|
+
blocking: true,
|
|
1134
|
+
title: 'Schema requirement does not support dependent requirements',
|
|
1135
|
+
issue: `Schema requirement ${schemaReq.id} is written as a closed/minimal schema list but omits ${missing.join(', ')}.`,
|
|
1136
|
+
action: 'Update the schema requirement before WP planning: add the missing schema support, or explicitly state the alternative Phase 1 storage contract and defer the dependent behavior.',
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function addSchemaAttributionWarningGate({ gates, sourceText, requirements }) {
|
|
1142
|
+
const schemaReq = findSchemaRequirement(requirements);
|
|
1143
|
+
if (!schemaReq) {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const schemaText = requirementFullText(schemaReq);
|
|
1147
|
+
const sourceMentionsAttribution = /(created_by|app\.current_user_id|owner_id|entity_history|enforcement[- ]deferred)/i.test(sourceText);
|
|
1148
|
+
const missing = [
|
|
1149
|
+
['created_by', /created_by/i],
|
|
1150
|
+
['app.current_user_id', /app\.current_user_id/i],
|
|
1151
|
+
['owner_id', /owner_id/i],
|
|
1152
|
+
['entity_history', /entity_history/i],
|
|
1153
|
+
['enforcement-deferred', /enforcement[- ]deferred/i],
|
|
1154
|
+
].filter(([, pattern]) => !pattern.test(schemaText)).map(([name]) => name);
|
|
1155
|
+
if (sourceMentionsAttribution && missing.length) {
|
|
1156
|
+
gates.push({
|
|
1157
|
+
id: 'schema-attribution-nfr-warning',
|
|
1158
|
+
severity: 'planning_hint',
|
|
1159
|
+
blocking: false,
|
|
1160
|
+
title: 'Schema attribution/access fields are not explicit',
|
|
1161
|
+
issue: `Sources mention attribution/access schema decisions, but schema requirement ${schemaReq.id} omits ${missing.join(', ')}.`,
|
|
1162
|
+
action: 'Before WP-001, add acceptance for created_by/app.current_user_id, owner_id, entity_history and deferred role enforcement if these are Phase 1 schema foundations.',
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function findSchemaRequirement(requirements) {
|
|
1168
|
+
return requirements.find((req) => /(schema|migration|tables?|таблиц|схем)/i.test(requirementFullText(req)));
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function requirementFullText(req) {
|
|
1172
|
+
return [
|
|
1173
|
+
req.id,
|
|
1174
|
+
req.text,
|
|
1175
|
+
req.acceptance,
|
|
1176
|
+
req.notes,
|
|
1177
|
+
req.workPackage,
|
|
1178
|
+
].filter(Boolean).join('\n');
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1103
1181
|
function addSequencingGate({ gates, sourceText, requirementText }) {
|
|
1104
1182
|
const hasGoldenSet = /(golden set|golden-set|regression baseline|baseline regression)/i.test(`${sourceText}\n${requirementText}`);
|
|
1105
1183
|
const hasMeetingExtraction = /(process-meeting|process meeting|\/process-meeting|meeting extraction)/i.test(requirementText);
|
package/bin/initiative.test.mjs
CHANGED
|
@@ -497,6 +497,102 @@ describe('initiative framework', () => {
|
|
|
497
497
|
expect(gate.action).toContain('client+internal');
|
|
498
498
|
expect(gate.action).toContain('related_person_id');
|
|
499
499
|
});
|
|
500
|
+
|
|
501
|
+
it('blocks planning when closed schema requirements do not support dependent approved requirements', () => {
|
|
502
|
+
const root = makeProject();
|
|
503
|
+
const docsDir = path.join(root, 'docs', 'delivery-os');
|
|
504
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
505
|
+
fs.writeFileSync(path.join(docsDir, '06-roadmap.md'), [
|
|
506
|
+
'# Phase 1',
|
|
507
|
+
'',
|
|
508
|
+
'- Migration 001b: person_aliases. Meeting fields: meeting_track, series_id, asr_quality, speaker_resolution_status.',
|
|
509
|
+
].join('\n'));
|
|
510
|
+
fs.writeFileSync(path.join(docsDir, '07-adrs.md'), [
|
|
511
|
+
'# ADR-6',
|
|
512
|
+
'',
|
|
513
|
+
'- Schema-ready enforcement-deferred: created_by, app.current_user_id, owner_id, entity_history.',
|
|
514
|
+
].join('\n'));
|
|
515
|
+
createInitiative({
|
|
516
|
+
projectRoot: root,
|
|
517
|
+
initiativeId: 'delivery-os-mvp',
|
|
518
|
+
title: 'Delivery OS MVP',
|
|
519
|
+
});
|
|
520
|
+
initiativeIntake({
|
|
521
|
+
projectRoot: root,
|
|
522
|
+
initiativeId: 'delivery-os-mvp',
|
|
523
|
+
docsDir: 'docs/delivery-os',
|
|
524
|
+
phase: 'phase-1',
|
|
525
|
+
});
|
|
526
|
+
const initiativeDir = path.join(root, 'ops', 'agent-pipeline', 'initiatives', 'delivery-os-mvp');
|
|
527
|
+
fs.writeFileSync(path.join(initiativeDir, 'requirements-map.md'), [
|
|
528
|
+
'# Requirements Map',
|
|
529
|
+
'',
|
|
530
|
+
'## Requirement Details',
|
|
531
|
+
'',
|
|
532
|
+
'### REQ-002',
|
|
533
|
+
'',
|
|
534
|
+
'- Status: `approved`',
|
|
535
|
+
'- Source: `docs/delivery-os/04-data-model.md:1206`',
|
|
536
|
+
'- Source hash: `sha`',
|
|
537
|
+
'- Phase: `phase-1`',
|
|
538
|
+
'- Quality: `complete`',
|
|
539
|
+
'- Quality issue: (none)',
|
|
540
|
+
'- Work package: WP-001',
|
|
541
|
+
'- Acceptance: Schema includes only team_members, clients, people, meetings, evidence, change_groups, proposed_changes, skill_executions, entity_history.',
|
|
542
|
+
'- Decision: `approve`',
|
|
543
|
+
'- Notes: Human-approved minimal schema.',
|
|
544
|
+
'- Requirement: Minimal Phase 1 schema includes only team_members, clients, people, meetings, evidence, change_groups, proposed_changes, skill_executions, entity_history.',
|
|
545
|
+
'',
|
|
546
|
+
'### REQ-003',
|
|
547
|
+
'',
|
|
548
|
+
'- Status: `approved`',
|
|
549
|
+
'- Source: `docs/delivery-os/06-roadmap.md:10`',
|
|
550
|
+
'- Source hash: `sha`',
|
|
551
|
+
'- Phase: `phase-1`',
|
|
552
|
+
'- Quality: `complete`',
|
|
553
|
+
'- Quality issue: (none)',
|
|
554
|
+
'- Work package: WP-003',
|
|
555
|
+
'- Acceptance: Meetings are classified with `meeting_track`.',
|
|
556
|
+
'- Decision: `approve`',
|
|
557
|
+
'- Notes: (empty)',
|
|
558
|
+
'- Requirement: Phase 1 classifies meetings by track using meeting_track.',
|
|
559
|
+
'',
|
|
560
|
+
'### REQ-004',
|
|
561
|
+
'',
|
|
562
|
+
'- Status: `approved`',
|
|
563
|
+
'- Source: `docs/delivery-os/06-roadmap.md:11`',
|
|
564
|
+
'- Source hash: `sha`',
|
|
565
|
+
'- Phase: `phase-1`',
|
|
566
|
+
'- Quality: `complete`',
|
|
567
|
+
'- Quality issue: (none)',
|
|
568
|
+
'- Work package: WP-001, WP-003',
|
|
569
|
+
'- Acceptance: Speaker resolution stores raw speaker labels and aliases and does not attribute unresolved aliases automatically.',
|
|
570
|
+
'- Decision: `approve`',
|
|
571
|
+
'- Notes: (empty)',
|
|
572
|
+
'- Requirement: Phase 1 supports speaker identity resolution by storing raw speaker labels and aliases.',
|
|
573
|
+
'',
|
|
574
|
+
].join('\n'));
|
|
575
|
+
|
|
576
|
+
const synthesis = initiativeRequirementsSynthesis({
|
|
577
|
+
projectRoot: root,
|
|
578
|
+
initiativeId: 'delivery-os-mvp',
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const schemaGate = synthesis.gates.find((gate) => gate.id === 'schema-supports-derived-requirements');
|
|
582
|
+
const attributionGate = synthesis.gates.find((gate) => gate.id === 'schema-attribution-nfr-warning');
|
|
583
|
+
expect(schemaGate).toMatchObject({
|
|
584
|
+
severity: 'critical',
|
|
585
|
+
blocking: true,
|
|
586
|
+
});
|
|
587
|
+
expect(schemaGate.issue).toContain('person_aliases');
|
|
588
|
+
expect(schemaGate.issue).toContain('meeting_track');
|
|
589
|
+
expect(schemaGate.issue).toContain('series_id');
|
|
590
|
+
expect(attributionGate).toMatchObject({
|
|
591
|
+
severity: 'planning_hint',
|
|
592
|
+
blocking: false,
|
|
593
|
+
});
|
|
594
|
+
expect(synthesis.rewriteRequired).toBe(true);
|
|
595
|
+
});
|
|
500
596
|
});
|
|
501
597
|
|
|
502
598
|
function makeProject() {
|