@sun-asterisk/sungen 3.3.0-alpha.4 → 3.3.0-alpha.6
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/dist/cli/commands/next.d.ts +3 -2
- package/dist/cli/commands/next.d.ts.map +1 -1
- package/dist/cli/commands/next.js +7 -1
- package/dist/cli/commands/next.js.map +1 -1
- package/dist/cli/commands/triage.d.ts +2 -1
- package/dist/cli/commands/triage.d.ts.map +1 -1
- package/dist/cli/commands/triage.js +42 -2
- package/dist/cli/commands/triage.js.map +1 -1
- package/dist/decisions/decision-schema.d.ts +2 -1
- package/dist/decisions/decision-schema.d.ts.map +1 -1
- package/dist/decisions/decision-schema.js +2 -1
- package/dist/decisions/decision-schema.js.map +1 -1
- package/dist/graph/manual-triage-decisions.d.ts +19 -0
- package/dist/graph/manual-triage-decisions.d.ts.map +1 -0
- package/dist/graph/manual-triage-decisions.js +53 -0
- package/dist/graph/manual-triage-decisions.js.map +1 -0
- package/dist/graph/nodes/test-case-nodes.d.ts +3 -0
- package/dist/graph/nodes/test-case-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/test-case-nodes.js +18 -1
- package/dist/graph/nodes/test-case-nodes.js.map +1 -1
- package/dist/graph/test-case-results.d.ts +1 -0
- package/dist/graph/test-case-results.d.ts.map +1 -1
- package/dist/graph/test-case-results.js +27 -3
- package/dist/graph/test-case-results.js.map +1 -1
- package/dist/graph/test-case-satisfied.d.ts +5 -4
- package/dist/graph/test-case-satisfied.d.ts.map +1 -1
- package/dist/graph/test-case-satisfied.js +4 -0
- package/dist/graph/test-case-satisfied.js.map +1 -1
- package/dist/graph/test-case-state-shape.d.ts +7 -1
- package/dist/graph/test-case-state-shape.d.ts.map +1 -1
- package/dist/graph/unit-results-freshness.d.ts +29 -0
- package/dist/graph/unit-results-freshness.d.ts.map +1 -0
- package/dist/graph/unit-results-freshness.js +213 -0
- package/dist/graph/unit-results-freshness.js.map +1 -0
- package/dist/harness/audit.d.ts +3 -2
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +4 -17
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/defect-draft.d.ts.map +1 -1
- package/dist/harness/defect-draft.js +12 -2
- package/dist/harness/defect-draft.js.map +1 -1
- package/dist/harness/defects.d.ts +14 -0
- package/dist/harness/defects.d.ts.map +1 -1
- package/dist/harness/defects.js +14 -10
- package/dist/harness/defects.js.map +1 -1
- package/dist/harness/flow-check.d.ts +9 -0
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +11 -0
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/gates/audit-gate-verdict.d.ts.map +1 -1
- package/dist/harness/gates/audit-gate-verdict.js +2 -1
- package/dist/harness/gates/audit-gate-verdict.js.map +1 -1
- package/dist/harness/gates/audit-input-hash.d.ts +2 -0
- package/dist/harness/gates/audit-input-hash.d.ts.map +1 -1
- package/dist/harness/gates/audit-input-hash.js +30 -2
- package/dist/harness/gates/audit-input-hash.js.map +1 -1
- package/dist/harness/gates/audit-report-verification.js +1 -1
- package/dist/harness/gates/audit-report-verification.js.map +1 -1
- package/dist/harness/gates/delivery-checklist.d.ts +3 -0
- package/dist/harness/gates/delivery-checklist.d.ts.map +1 -1
- package/dist/harness/gates/delivery-checklist.js +28 -3
- package/dist/harness/gates/delivery-checklist.js.map +1 -1
- package/dist/harness/gates/in-run-decision-check.d.ts.map +1 -1
- package/dist/harness/gates/in-run-decision-check.js +14 -1
- package/dist/harness/gates/in-run-decision-check.js.map +1 -1
- package/dist/harness/next-step.d.ts +3 -0
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +7 -0
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/triage-manual-classification.d.ts +32 -0
- package/dist/harness/triage-manual-classification.d.ts.map +1 -0
- package/dist/harness/triage-manual-classification.js +56 -0
- package/dist/harness/triage-manual-classification.js.map +1 -0
- package/dist/harness/unit-paths.d.ts +8 -0
- package/dist/harness/unit-paths.d.ts.map +1 -1
- package/dist/harness/unit-paths.js +23 -1
- package/dist/harness/unit-paths.js.map +1 -1
- package/dist/hooks/guard-tty-spoof-rule.d.ts +2 -1
- package/dist/hooks/guard-tty-spoof-rule.d.ts.map +1 -1
- package/dist/hooks/guard-tty-spoof-rule.js +3 -2
- package/dist/hooks/guard-tty-spoof-rule.js.map +1 -1
- package/package.json +3 -3
- package/src/cli/commands/next.ts +9 -3
- package/src/cli/commands/triage.ts +38 -2
- package/src/decisions/decision-schema.ts +2 -1
- package/src/graph/manual-triage-decisions.ts +55 -0
- package/src/graph/nodes/test-case-nodes.ts +19 -1
- package/src/graph/test-case-results.ts +25 -4
- package/src/graph/test-case-satisfied.ts +7 -4
- package/src/graph/test-case-state-shape.ts +7 -2
- package/src/graph/unit-results-freshness.ts +165 -0
- package/src/harness/audit.ts +4 -13
- package/src/harness/defect-draft.ts +13 -2
- package/src/harness/defects.ts +22 -9
- package/src/harness/flow-check.ts +11 -0
- package/src/harness/gates/audit-gate-verdict.ts +2 -1
- package/src/harness/gates/audit-input-hash.ts +27 -3
- package/src/harness/gates/audit-report-verification.ts +2 -2
- package/src/harness/gates/delivery-checklist.ts +26 -4
- package/src/harness/gates/in-run-decision-check.ts +15 -1
- package/src/harness/next-step.ts +7 -0
- package/src/harness/triage-manual-classification.ts +76 -0
- package/src/harness/unit-paths.ts +19 -0
- package/src/hooks/guard-tty-spoof-rule.ts +5 -3
package/src/harness/next-step.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
countFailedResults, countScenarios, hasDeliverableFor, hasResolvedSelector, isCompiled, readIfExists, unitResultFiles,
|
|
28
28
|
} from '../graph/unit-artifact-probes';
|
|
29
29
|
import { AuditNotCounted, ContractFacts, ContractSummary, readContractFacts, readTrustedAudit } from '../graph/unit-state-facts';
|
|
30
|
+
import { ResultsFreshness, resultsFreshness } from '../graph/unit-results-freshness';
|
|
30
31
|
|
|
31
32
|
export interface NextStep {
|
|
32
33
|
/** The command to run, as the operator would type it. */
|
|
@@ -89,6 +90,8 @@ export interface UnitState {
|
|
|
89
90
|
contract?: ContractSummary;
|
|
90
91
|
/** Per-test-case state (DOS-11.1); absent when not read from disk or unreadable. */
|
|
91
92
|
testCases?: TestCaseState[];
|
|
93
|
+
/** Which run the results come from and what they are older than (#942); absent without results. Advisory, decides no step. */
|
|
94
|
+
results?: ResultsFreshness;
|
|
92
95
|
steps: NextStep[];
|
|
93
96
|
}
|
|
94
97
|
|
|
@@ -162,6 +165,10 @@ export function readUnitState(
|
|
|
162
165
|
state.testCases = testCaseStates(projectRoot, unit, { unitDir, auditFindings: state.audit?.findings ?? [] })
|
|
163
166
|
.map((tc) => (contract?.cases.has(tc.id) ? { ...tc, contract: contract.cases.get(tc.id) } : tc));
|
|
164
167
|
} catch { /* an unparseable .feature — the unit-level nodes still answer, as before test-case state */ }
|
|
168
|
+
try {
|
|
169
|
+
const results = resultsFreshness(projectRoot, unitDir, unit);
|
|
170
|
+
if (results) state.results = results;
|
|
171
|
+
} catch { /* advisory: results that cannot be dated add nothing */ }
|
|
165
172
|
state.steps = deriveSteps(state);
|
|
166
173
|
return state;
|
|
167
174
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen triage --screen <u> --case <id> --as product|test-defect|environment --reason "…"` (#928):
|
|
3
|
+
* classify one red by hand — the one `sungen triage` left unclassified, or one it got wrong — as a
|
|
4
|
+
* `triage-classification` decision in the unit's `decisions/` (read back by graph/manual-triage-decisions.ts).
|
|
5
|
+
*
|
|
6
|
+
* validated first the class, a reason, a case that exists, is red and is not exempt — before any
|
|
7
|
+
* prompt, so a human never confirms something that is then rejected
|
|
8
|
+
* product stricter than any automatic class (it leads to a QA decision): anyone may record it
|
|
9
|
+
* test-defect, can hide a product defect behind "fix the test" / "re-run": a human types a
|
|
10
|
+
* environment confirmation on a terminal (DOS-13.4); without one it is refused, nothing is
|
|
11
|
+
* written and the exact command is printed. Only after the typed phrase does the
|
|
12
|
+
* entry carry `humanConfirmed: true`, and a reader ignores one without it
|
|
13
|
+
* bound evidence_hash = the case's red as it reads now; a re-run voids the classification
|
|
14
|
+
*/
|
|
15
|
+
import type { AppendResult } from '../decisions/append-decision';
|
|
16
|
+
import { appendDecision } from '../decisions/append-decision';
|
|
17
|
+
import type { DecisionDraft } from '../decisions/decision-schema';
|
|
18
|
+
import { gitUserName } from '../decisions/decision-source';
|
|
19
|
+
import { ConfirmationIO, formatCommand, requireTypedConfirmation } from '../decisions/human-confirmation';
|
|
20
|
+
import { resolveUnitCandidates } from '../cli/resolve-unit';
|
|
21
|
+
import { HUMAN_ONLY_TRIAGE, MANUAL_TRIAGE_AS, ManualTriageClass, TRIAGE_DECISION_KIND } from '../graph/manual-triage-decisions';
|
|
22
|
+
import { exemptionOf } from '../graph/test-case-satisfied';
|
|
23
|
+
import { testCaseStates } from '../graph/test-case-states';
|
|
24
|
+
import { unitScopeOfDir } from './gates/unit-gate-overrides';
|
|
25
|
+
|
|
26
|
+
export interface ClassifyRedCaseInput {
|
|
27
|
+
projectRoot: string;
|
|
28
|
+
unit: string;
|
|
29
|
+
caseId: string;
|
|
30
|
+
/** product | test-defect | environment */
|
|
31
|
+
as: string;
|
|
32
|
+
reason?: string;
|
|
33
|
+
io: ConfirmationIO;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ClassifyRedCaseResult extends AppendResult { caseId: string; class: ManualTriageClass }
|
|
37
|
+
|
|
38
|
+
export async function classifyRedCase(i: ClassifyRedCaseInput): Promise<ClassifyRedCaseResult> {
|
|
39
|
+
const cls = Object.prototype.hasOwnProperty.call(MANUAL_TRIAGE_AS, i.as) ? MANUAL_TRIAGE_AS[i.as] : undefined;
|
|
40
|
+
if (!cls) throw new Error(`--as must be one of ${Object.keys(MANUAL_TRIAGE_AS).join(', ')} (got "${i.as}")`);
|
|
41
|
+
const reason = (i.reason ?? '').trim();
|
|
42
|
+
if (!reason) throw new Error('--reason is required: a classification records WHY this red is what you say it is');
|
|
43
|
+
const found = resolveUnitCandidates(i.unit, i.projectRoot);
|
|
44
|
+
if (found.length !== 1) throw new Error(`"${i.unit}" does not name exactly one unit under qa/screens, qa/flows or qa/api`);
|
|
45
|
+
const unitDir = found[0].dir;
|
|
46
|
+
const scope = unitScopeOfDir(i.projectRoot, unitDir);
|
|
47
|
+
if (!scope) throw new Error(`"${i.unit}" has no decisions scope`);
|
|
48
|
+
const cases = testCaseStates(i.projectRoot, i.unit, { unitDir, auditFindings: [] });
|
|
49
|
+
const tc = cases.find((c) => c.id === i.caseId);
|
|
50
|
+
if (!tc) {
|
|
51
|
+
const known = cases.slice(0, 8).map((c) => c.id).join(', ');
|
|
52
|
+
throw new Error(`no test case "${i.caseId}" in ${i.unit}${known ? ` — known: ${known}${cases.length > 8 ? ', …' : ''}` : ''}`);
|
|
53
|
+
}
|
|
54
|
+
if (tc.result !== 'failed' || !tc.evidence) throw new Error(`${tc.id} has no red result to classify (result: ${tc.result})`);
|
|
55
|
+
const exempt = exemptionOf(tc);
|
|
56
|
+
if (exempt) throw new Error(`${tc.id} is exempt (${exempt}) — it needs no classification`);
|
|
57
|
+
|
|
58
|
+
const human = HUMAN_ONLY_TRIAGE.has(cls);
|
|
59
|
+
if (human) {
|
|
60
|
+
await requireTypedConfirmation({
|
|
61
|
+
what: `classify the red ${tc.id} of ${i.unit} as ${cls}`,
|
|
62
|
+
phrase: tc.id,
|
|
63
|
+
command: formatCommand(['sungen', 'triage', found[0].kind === 'api' ? '--api' : '--screen', i.unit, '--case', tc.id, '--as', i.as, '--reason', reason]),
|
|
64
|
+
summary: [`case: ${tc.title}`, `automatic triage: ${tc.triage ? `${tc.triage.class} (${tc.triage.ruleId})` : 'none current'}`, `reason: ${reason}`,
|
|
65
|
+
`recorded by: ${gitUserName(i.projectRoot)}`, `as ${cls} it leaves the product-defect path — confirm only after reading its evidence (trace, error)`,
|
|
66
|
+
'it stops counting as soon as the case runs again'],
|
|
67
|
+
io: i.io,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const draft: DecisionDraft = {
|
|
71
|
+
kind: TRIAGE_DECISION_KIND, anchors: [], text: `${tc.id} is ${cls}: ${tc.title}`, reason,
|
|
72
|
+
payload: { case: tc.id, class: cls, ...(human ? { humanConfirmed: true } : {}) },
|
|
73
|
+
source: { by: gitUserName(i.projectRoot), where: 'cli' }, valid_from: new Date().toISOString(), visibility: 'internal', evidence_hash: tc.evidence,
|
|
74
|
+
};
|
|
75
|
+
return { ...appendDecision(i.projectRoot, scope, draft), caseId: tc.id, class: cls };
|
|
76
|
+
}
|
|
@@ -49,3 +49,22 @@ export function featureFilesFor(unitDir: string, unit: string): string[] {
|
|
|
49
49
|
...names.filter((n) => n !== canonicalName),
|
|
50
50
|
].map((n) => path.join(dir, n));
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The catalog-resolution id for a unit dir (relative to qa/): screen · flows/<flow> · api/<area> ·
|
|
55
|
+
* api/flows/<flow>; a bare screen is its basename. Shared by the audit, which scores and links the
|
|
56
|
+
* unit under this id, and its inputsHash, which reads what the audit reads under the same id.
|
|
57
|
+
*/
|
|
58
|
+
export function catalogIdFromScreenDir(screenDir: string): string {
|
|
59
|
+
const parts = screenDir.split(path.sep);
|
|
60
|
+
const qa = parts.lastIndexOf('qa');
|
|
61
|
+
if (qa >= 0) {
|
|
62
|
+
if (parts[qa + 1] === 'api' && parts[qa + 2] === 'flows' && parts[qa + 3]) return `api/flows/${parts[qa + 3]}`;
|
|
63
|
+
if (parts[qa + 1] === 'api' && parts[qa + 2]) return `api/${parts[qa + 2]}`;
|
|
64
|
+
if (parts[qa + 1] === 'flows' && parts[qa + 2]) return `flows/${parts[qa + 2]}`;
|
|
65
|
+
}
|
|
66
|
+
return path.basename(screenDir);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A screen unit — the only kind a sibling flow credits (TQ-4); flows and api units are not. */
|
|
70
|
+
export const isScreenUnitId = (catalogId: string): boolean => !/^(flows|api)\//.test(catalogId);
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Guard rule `tty-spoof` (phase 06, M5): ASK before an agent runs a human-confirmed sungen command
|
|
3
3
|
* through a terminal emulator.
|
|
4
4
|
*
|
|
5
|
-
* `contract approve`, `journey --waive`, `journey --decide
|
|
5
|
+
* `contract approve`, `journey --waive`, `journey --decide`, `journey --signoff` and `triage --as test-defect|environment`
|
|
6
|
+
* (#928: a red classified away from the product-defect path) refuse without a TTY (DOS-13.4), and
|
|
6
7
|
* an agent's Bash has none — unless it borrows one: `script`, `expect`, `unbuffer`, or a pty from
|
|
7
8
|
* Python/Perl/zsh. Then the prompt sees a "terminal" and a piped phrase reads as a human typing it.
|
|
8
9
|
* This rule spots the pair in the raw command (quotes included, so `bash -c "…"` and `expect -c '…'`
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
import type { GuardRule } from './guard-rules';
|
|
14
15
|
|
|
15
16
|
const TTY_EMULATOR = /\b(?:script|unbuffer|expect|pty|zpty|openpty|forkpty|ptyprocess)\b/i;
|
|
16
|
-
const HUMAN_GATED = /\bsungen\b[\s\S]*?\b(?:contract['",\s]+approve\b|journey\b[\s\S]*?--(?:waive|decide|signoff)\b)/i;
|
|
17
|
+
const HUMAN_GATED = /\bsungen\b[\s\S]*?\b(?:contract['",\s]+approve\b|journey\b[\s\S]*?--(?:waive|decide|signoff)\b|triage\b[\s\S]*?--as[\s='",]+(?:test-defect|environment)\b)/i;
|
|
17
18
|
|
|
18
19
|
export const ttySpoof: GuardRule = {
|
|
19
20
|
id: 'tty-spoof',
|
|
@@ -23,7 +24,8 @@ export const ttySpoof: GuardRule = {
|
|
|
23
24
|
if (!cmd || !TTY_EMULATOR.test(cmd) || !HUMAN_GATED.test(cmd)) return null;
|
|
24
25
|
return {
|
|
25
26
|
status: 'ask',
|
|
26
|
-
askReason: 'sungen: this runs a human-confirmed sungen action (`contract approve` / `journey --waive|--decide|--signoff`
|
|
27
|
+
askReason: 'sungen: this runs a human-confirmed sungen action (`contract approve` / `journey --waive|--decide|--signoff` / '
|
|
28
|
+
+ '`triage --as test-defect|environment`) through a terminal '
|
|
27
29
|
+ 'emulator (script / expect / unbuffer / pty). Those confirmations exist so a PERSON at a terminal decides — allow only if you '
|
|
28
30
|
+ 'are that person and asked for exactly this; otherwise run the command yourself in your own terminal.',
|
|
29
31
|
};
|