@planu/cli 4.11.1 → 4.11.3
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 +16 -0
- package/dist/config/elicitation-questions.json +5 -17
- package/dist/engine/cascade-hooks/registry.js +0 -2
- package/dist/engine/challenge-scenarios-loader.d.ts +3 -3
- package/dist/engine/compliance-injector.d.ts +3 -3
- package/dist/engine/dashboard/kanban-logic.d.ts +1 -1
- package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +1 -1
- package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +1 -1
- package/dist/engine/elicitation/answer-extractor.d.ts +1 -4
- package/dist/engine/elicitation/answer-extractor.js +197 -68
- package/dist/engine/elicitation/decision-gap-detector.js +59 -79
- package/dist/engine/elicitation/index.d.ts +0 -1
- package/dist/engine/elicitation/index.js +0 -1
- package/dist/engine/elicitation/question-generator.js +10 -3
- package/dist/engine/elicitation/question-grounding-gate.js +83 -34
- package/dist/engine/evidence-gates/lifecycle-gate.js +27 -14
- package/dist/engine/evidence-index/index-builder.js +7 -2
- package/dist/engine/frontmatter-sha/frozen-fields.d.ts +1 -1
- package/dist/engine/handoff-artifacts/schemas/red-tests-handoff.d.ts +2 -2
- package/dist/engine/handoff-artifacts/schemas.d.ts +22 -22
- package/dist/engine/human-summary.js +3 -3
- package/dist/engine/implementation-contract/common.d.ts +1 -1
- package/dist/engine/implementation-contract/renderer.js +17 -39
- package/dist/engine/keyword-matcher/stopwords.d.ts +2 -2
- package/dist/engine/lifecycle-hints.d.ts +1 -1
- package/dist/engine/lifecycle-hints.js +13 -29
- package/dist/engine/provider-adapters/adapters/claude.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/gemini.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/gpt4.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/markdown.d.ts +1 -1
- package/dist/engine/safety/atomic-write-file.d.ts +1 -1
- package/dist/engine/safety/cross-process-lock.d.ts +1 -1
- package/dist/engine/safety/path-sanitizer.d.ts +1 -1
- package/dist/engine/security-analyzer/profile-detector.js +48 -16
- package/dist/engine/spec-format/copula-blacklist.d.ts +2 -2
- package/dist/engine/spec-format/lean-spec-generator.d.ts +2 -2
- package/dist/engine/spec-format/lean-spec-generator.js +9 -10
- package/dist/engine/spec-format/metadata-value-policy.d.ts +20 -20
- package/dist/engine/spec-generator/fallback-generator.js +13 -20
- package/dist/engine/spec-grounding/contract.d.ts +5 -0
- package/dist/engine/spec-grounding/contract.js +10 -6
- package/dist/engine/spec-migrator/lean-migration.js +9 -16
- package/dist/engine/text-signal-boundaries.js +45 -12
- package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
- package/dist/engine/web-fetcher/stack-advisor.d.ts +2 -2
- package/dist/engine/web-fetcher/stack-advisor.js +78 -17
- package/dist/engine/well-known/manifest-schema.d.ts +33 -33
- package/dist/engine/workers/schema.d.ts +3 -3
- package/dist/hosts/claude-code/ux/mcp-resources.d.ts +4 -4
- package/dist/hosts/codex/rest-shim.d.ts +1 -1
- package/dist/native/lightweight-command-catalog.d.ts +30 -30
- package/dist/server/openapi-spec.d.ts +132 -132
- package/dist/tools/challenge-spec/challenge-report.d.ts +13 -0
- package/dist/tools/challenge-spec/challenge-report.js +55 -0
- package/dist/tools/challenge-spec/event-challenge-scenarios.js +8 -27
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.js +16 -32
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-b.js +15 -20
- package/dist/tools/challenge-spec/scenario-collector.d.ts +13 -0
- package/dist/tools/challenge-spec/scenario-collector.js +86 -0
- package/dist/tools/challenge-spec/scenarios-data.js +10 -9
- package/dist/tools/challenge-spec/scenarios-failure.js +38 -54
- package/dist/tools/challenge-spec/scenarios-scale.js +14 -15
- package/dist/tools/challenge-spec/scenarios-security.d.ts +1 -1
- package/dist/tools/challenge-spec/scenarios-security.js +18 -11
- package/dist/tools/challenge-spec/scenarios-utils.d.ts +31 -5
- package/dist/tools/challenge-spec/scenarios-utils.js +102 -6
- package/dist/tools/challenge-spec/security-challenge-scenarios.d.ts +1 -1
- package/dist/tools/challenge-spec/security-challenge-scenarios.js +20 -25
- package/dist/tools/challenge-spec.js +46 -124
- package/dist/tools/clarify-requirements/multiple-choice.js +1 -1
- package/dist/tools/clarify-requirements/questions-context.js +11 -11
- package/dist/tools/clarify-requirements/questions.js +3 -23
- package/dist/tools/clarify-requirements.js +38 -0
- package/dist/tools/compliance-gate-handler.d.ts +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.js +29 -54
- package/dist/tools/create-spec/post-creation.js +23 -4
- package/dist/tools/create-spec.js +51 -168
- package/dist/tools/delete-first/suggest-deletions.d.ts +1 -1
- package/dist/tools/elicit-requirements-handler.js +20 -12
- package/dist/tools/generate-batch-script.d.ts +2 -2
- package/dist/tools/init-project/rules-generator.js +0 -1
- package/dist/tools/oauth-handler.d.ts +1 -1
- package/dist/tools/register-sdd-tools.d.ts +1 -1
- package/dist/tools/register-spec-tools/core-spec-tools.js +11 -2
- package/dist/tools/render-spec-for-provider.d.ts +2 -2
- package/dist/tools/scan-orphan-spec-refs.d.ts +6 -6
- package/dist/tools/schemas/agents.d.ts +7 -7
- package/dist/tools/schemas/analysis.d.ts +11 -11
- package/dist/tools/schemas/delete.d.ts +5 -5
- package/dist/tools/schemas/github.d.ts +4 -4
- package/dist/tools/schemas/ide-config.d.ts +12 -12
- package/dist/tools/schemas/infra.d.ts +9 -9
- package/dist/tools/schemas/lifecycle.d.ts +50 -50
- package/dist/tools/schemas/llm-provider-schemas.d.ts +7 -7
- package/dist/tools/schemas/plugins-schemas.d.ts +12 -12
- package/dist/tools/schemas/project-graph.d.ts +6 -6
- package/dist/tools/schemas/rbac.d.ts +2 -2
- package/dist/tools/schemas/registry.d.ts +3 -3
- package/dist/tools/schemas/runtime-security.d.ts +2 -2
- package/dist/tools/schemas/session.d.ts +2 -2
- package/dist/tools/schemas/spec.d.ts +15 -15
- package/dist/tools/schemas/token-intelligence.d.ts +6 -6
- package/dist/tools/schemas/token-optimization.d.ts +2 -2
- package/dist/tools/schemas/workers-schema.d.ts +4 -4
- package/dist/tools/skill-bootstrap-handler.d.ts +7 -7
- package/dist/tools/suggest-stack.js +49 -13
- package/dist/tools/tool-registry/deprecated-stubs.d.ts +1 -1
- package/dist/tools/triage-request.d.ts +3 -3
- package/dist/tools/update-status/dod-gates.d.ts +8 -0
- package/dist/tools/update-status/dod-gates.js +68 -43
- package/dist/tools/update-status/file-sync.d.ts +1 -1
- package/dist/tools/update-status/file-sync.js +10 -45
- package/dist/tools/update-status/index.js +133 -97
- package/dist/tools/update-status/qa-gate.js +26 -0
- package/dist/tools/update-status/response-builder.d.ts +1 -1
- package/dist/tools/update-status/response-builder.js +11 -19
- package/dist/tools/update-status/transition-guard.d.ts +2 -2
- package/dist/tools/update-status/transition-guard.js +11 -8
- package/dist/tools/update-status-actions.d.ts +0 -1
- package/dist/tools/update-status-actions.js +10 -26
- package/dist/tools/update-status-convention-gate.d.ts +1 -6
- package/dist/tools/update-status-convention-gate.js +9 -33
- package/dist/tools/validate-lint.d.ts +1 -1
- package/dist/tools/validate-lint.js +39 -1
- package/dist/types/docs.d.ts +0 -4
- package/dist/types/env.d.ts +0 -4
- package/dist/types/estimation.d.ts +0 -2
- package/dist/types/multi-teammate-review.d.ts +4 -4
- package/dist/types/plugin-install.d.ts +7 -7
- package/dist/types/spec/core.d.ts +20 -0
- package/dist/types/spec/inputs.d.ts +1 -1
- package/dist/types/stack/index.d.ts +1 -1
- package/dist/types/stack/recommend.d.ts +10 -0
- package/package.json +21 -18
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
- package/src/i18n/messages/en.json +0 -72
- package/src/i18n/messages/es.json +0 -72
- package/src/i18n/messages/pt.json +0 -72
- package/dist/config/elicitation-dimensions.json +0 -51
- package/dist/engine/actuals-estimator.d.ts +0 -14
- package/dist/engine/actuals-estimator.js +0 -64
- package/dist/engine/cascade-hooks/hooks/living-specs.hook.d.ts +0 -3
- package/dist/engine/cascade-hooks/hooks/living-specs.hook.js +0 -19
- package/dist/engine/elicitation/non-interactive-defaults.d.ts +0 -9
- package/dist/engine/elicitation/non-interactive-defaults.js +0 -28
- package/dist/engine/elicitation/option-builder.d.ts +0 -9
- package/dist/engine/elicitation/option-builder.js +0 -85
- package/dist/engine/progress-writer.d.ts +0 -10
- package/dist/engine/progress-writer.js +0 -63
- package/dist/tools/clarify-requirements/interview-mode.d.ts +0 -42
- package/dist/tools/clarify-requirements/interview-mode.js +0 -342
- package/dist/tools/update-status/mode-hints.d.ts +0 -4
- package/dist/tools/update-status/mode-hints.js +0 -24
|
@@ -2,30 +2,13 @@
|
|
|
2
2
|
// Generates backward-compatibility contract and idempotence challenges
|
|
3
3
|
// for specs that produce or consume events.
|
|
4
4
|
import { detectBrokers, getBrokerConfig, requiresIdempotence, } from '../../engine/event-detector.js';
|
|
5
|
+
import { detectChallengeCapabilities } from './scenarios-utils.js';
|
|
5
6
|
/**
|
|
6
7
|
* Returns true when the spec or project has event-driven characteristics.
|
|
7
8
|
*/
|
|
8
9
|
export function isEventDrivenSpec(spec, specContent, knowledge) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
lower.includes('kafka') ||
|
|
12
|
-
lower.includes('rabbitmq') ||
|
|
13
|
-
lower.includes('pubsub') ||
|
|
14
|
-
lower.includes('nats') ||
|
|
15
|
-
lower.includes('eventbridge') ||
|
|
16
|
-
lower.includes('sqs') ||
|
|
17
|
-
lower.includes('sns') ||
|
|
18
|
-
lower.includes('message') ||
|
|
19
|
-
lower.includes('consumer') ||
|
|
20
|
-
lower.includes('producer');
|
|
21
|
-
if (spec.target === 'frontend' && !specSignals) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
const brokers = detectBrokers(knowledge.stack);
|
|
25
|
-
if (specSignals) {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
return brokers.length > 0 && brokers[0] !== 'unknown' && spec.target !== 'frontend';
|
|
10
|
+
void knowledge;
|
|
11
|
+
return detectChallengeCapabilities(spec, specContent).events;
|
|
29
12
|
}
|
|
30
13
|
/**
|
|
31
14
|
* Generate event-driven failure scenarios for challenge_spec.
|
|
@@ -50,7 +33,7 @@ export function generateEventChallengeScenarios(spec, specContent, knowledge) {
|
|
|
50
33
|
impact: 'critical',
|
|
51
34
|
currentHandling: 'Schema change may not have a formal contract review — consumers may fail silently with unrecognized fields or crash on missing ones.',
|
|
52
35
|
requiredHandling: 'Classify the change: additive (new optional field — safe), breaking (removed/type-changed field — requires dual-version emission), or deprecation (field marked deprecated with a sunset date). ' +
|
|
53
|
-
'For breaking changes: emit both old and new versions during the transition period
|
|
36
|
+
'For breaking changes: emit both old and new versions during the transition period defined by the rollout plan. ' +
|
|
54
37
|
'Update the event contract in event-contracts/ before merging.',
|
|
55
38
|
dataConsistency: 'If the change is breaking and dual-version is not implemented, consumers will receive invalid events and may write corrupt state.',
|
|
56
39
|
userExperience: 'Invisible to end users, but breaking event changes cause downstream service failures that eventually surface as user-facing errors.',
|
|
@@ -62,9 +45,7 @@ export function generateEventChallengeScenarios(spec, specContent, knowledge) {
|
|
|
62
45
|
probability: 'high',
|
|
63
46
|
impact: 'high',
|
|
64
47
|
currentHandling: 'Consumer may lack a deduplication check — processing the same event twice could double-charge, double-ship, or create duplicate records.',
|
|
65
|
-
requiredHandling: '
|
|
66
|
-
'Check at the start of each consumer handler: if ID already processed, acknowledge and skip. ' +
|
|
67
|
-
'Choose the deduplication window based on expected redelivery window (typically 24h–7d).',
|
|
48
|
+
requiredHandling: 'Define the deduplication identifier, durable boundary, atomicity rule, and retention window from the event contract.',
|
|
68
49
|
dataConsistency: 'The idempotency record must be written atomically with the side-effect (same DB transaction or two-phase commit) to avoid the "processed but not recorded" gap.',
|
|
69
50
|
userExperience: 'Idempotence failure is invisible until users report duplicates (duplicate orders, double payments, etc.) — prevent, do not detect.',
|
|
70
51
|
});
|
|
@@ -72,13 +53,13 @@ export function generateEventChallengeScenarios(spec, specContent, knowledge) {
|
|
|
72
53
|
// 3. DLQ poisoned message challenge
|
|
73
54
|
if (brokerConfig?.dlqSupport && brokerConfig.dlqSupport !== '') {
|
|
74
55
|
scenarios.push({
|
|
75
|
-
scenario: `[${spec.id}] DLQ Poisoned Message: a consumer receives an event with a malformed payload
|
|
56
|
+
scenario: `[${spec.id}] DLQ Poisoned Message: a consumer receives an event with a malformed payload. Does the consumer retry indefinitely and block the queue? Which declared failure condition moves the message to the DLQ?`,
|
|
76
57
|
probability: 'medium',
|
|
77
58
|
impact: 'high',
|
|
78
59
|
currentHandling: 'Consumer may retry indefinitely on schema validation errors, blocking queue processing for all subsequent events.',
|
|
79
|
-
requiredHandling: '
|
|
60
|
+
requiredHandling: 'Use the retry limit and delay policy declared by the event contract, then route terminal failures with enough diagnostic context. ' +
|
|
80
61
|
`DLQ support: ${brokerConfig.dlqSupport}. ` +
|
|
81
|
-
'
|
|
62
|
+
'Define the alert threshold and re-injection procedure from the operations contract.',
|
|
82
63
|
dataConsistency: 'Messages in the DLQ must be investigated and re-injected only after the root cause is fixed — re-injecting without fix will loop back to DLQ.',
|
|
83
64
|
userExperience: 'DLQ accumulation means events are not processed — monitor and alert before SLA is breached.',
|
|
84
65
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// tools/challenge-spec/resilience-challenge-scenarios-a.ts — SPEC-034
|
|
2
2
|
// Signal detectors + validation boundary + cascade failure scenarios
|
|
3
|
+
import { contentMentions } from './scenarios-utils.js';
|
|
3
4
|
// Keywords indicating external service calls or shared state
|
|
4
5
|
export const EXTERNAL_CALL_KEYWORDS = [
|
|
5
6
|
'api',
|
|
@@ -70,36 +71,31 @@ export const SAGA_KEYWORDS = [
|
|
|
70
71
|
* Returns true when the spec describes an external service call.
|
|
71
72
|
*/
|
|
72
73
|
export function hasExternalCallSignal(spec, specContent) {
|
|
73
|
-
|
|
74
|
-
return EXTERNAL_CALL_KEYWORDS.some((kw) => lower.includes(kw));
|
|
74
|
+
return contentMentions(`${spec.title}\n${spec.tags.join(' ')}\n${specContent}`, EXTERNAL_CALL_KEYWORDS);
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Returns true when the spec describes input validation.
|
|
78
78
|
*/
|
|
79
79
|
export function hasValidationSignal(spec, specContent) {
|
|
80
|
-
|
|
81
|
-
return VALIDATION_KEYWORDS.some((kw) => lower.includes(kw));
|
|
80
|
+
return contentMentions(`${spec.title}\n${spec.tags.join(' ')}\n${specContent}`, VALIDATION_KEYWORDS);
|
|
82
81
|
}
|
|
83
82
|
/**
|
|
84
83
|
* Returns true when the spec is a rate-limit-sensitive endpoint.
|
|
85
84
|
*/
|
|
86
85
|
export function hasRateLimitSignal(spec, specContent) {
|
|
87
|
-
|
|
88
|
-
return RATE_LIMIT_KEYWORDS.some((kw) => lower.includes(kw));
|
|
86
|
+
return contentMentions(`${spec.title}\n${spec.tags.join(' ')}\n${specContent}`, RATE_LIMIT_KEYWORDS);
|
|
89
87
|
}
|
|
90
88
|
/**
|
|
91
89
|
* Returns true when the spec involves shared state.
|
|
92
90
|
*/
|
|
93
91
|
export function hasConcurrencySignal(spec, specContent) {
|
|
94
|
-
|
|
95
|
-
return CONCURRENCY_KEYWORDS.some((kw) => lower.includes(kw));
|
|
92
|
+
return contentMentions(`${spec.title}\n${spec.tags.join(' ')}\n${specContent}`, CONCURRENCY_KEYWORDS);
|
|
96
93
|
}
|
|
97
94
|
/**
|
|
98
95
|
* Returns true when the spec describes a distributed saga.
|
|
99
96
|
*/
|
|
100
97
|
export function hasSagaSignal(spec, specContent) {
|
|
101
|
-
|
|
102
|
-
return SAGA_KEYWORDS.some((kw) => lower.includes(kw));
|
|
98
|
+
return contentMentions(`${spec.title}\n${spec.tags.join(' ')}\n${specContent}`, SAGA_KEYWORDS);
|
|
103
99
|
}
|
|
104
100
|
/**
|
|
105
101
|
* Generate validation boundary challenge scenarios.
|
|
@@ -161,38 +157,26 @@ export function generateValidationBoundaryScenarios(spec) {
|
|
|
161
157
|
export function generateCascadeFailureScenarios(spec) {
|
|
162
158
|
return [
|
|
163
159
|
{
|
|
164
|
-
scenario: `[${spec.id}] Cascade Failure —
|
|
165
|
-
'
|
|
166
|
-
'
|
|
167
|
-
'If the pool has size N and each request holds a connection for T seconds, the pool fills in N * T seconds. ' +
|
|
168
|
-
'Calculate this with the actual pool size and timeout values. ' +
|
|
169
|
-
'Does the circuit breaker open before the pool is exhausted?',
|
|
160
|
+
scenario: `[${spec.id}] Cascade Failure — Dependency Latency Exhausts Local Capacity: ` +
|
|
161
|
+
'Which latency and capacity envelope is declared for the dependency? ' +
|
|
162
|
+
'Does failure isolation activate before workers, connections, or queued work are exhausted?',
|
|
170
163
|
probability: 'medium',
|
|
171
164
|
impact: 'critical',
|
|
172
|
-
currentHandling: '
|
|
173
|
-
|
|
174
|
-
requiredHandling: 'Set connect_timeout (2s) and read_timeout (10s) — never rely on OS timeout (30s+). ' +
|
|
175
|
-
'Circuit breaker must open before pool exhaustion: threshold 50% failures in 10 calls, open duration 60s. ' +
|
|
176
|
-
'When pool is at 80% capacity, emit a warning metric. ' +
|
|
177
|
-
'When circuit is open, return 503 immediately without touching the pool.',
|
|
165
|
+
currentHandling: 'Slow external services can exhaust local capacity and block unrelated work.',
|
|
166
|
+
requiredHandling: 'Define measurable connect/read bounds, local capacity, failure-isolation behavior, and alert thresholds from the dependency contract.',
|
|
178
167
|
dataConsistency: 'Pool exhaustion can cascade: if this service pool is exhausted, callers of this service also accumulate requests.',
|
|
179
|
-
userExperience: '
|
|
168
|
+
userExperience: 'Use the dependency-failure response and latency bound declared by the client contract.',
|
|
180
169
|
},
|
|
181
170
|
{
|
|
182
|
-
scenario: `[${spec.id}] Resilience —
|
|
183
|
-
'If
|
|
184
|
-
'all queued retries fire simultaneously. ' +
|
|
185
|
-
'Does the retry strategy use jitter (random delay offset) to spread the load, ' +
|
|
186
|
-
'or do all retries hit the recovering service at the same time (thundering herd)?',
|
|
171
|
+
scenario: `[${spec.id}] Resilience — Coordinated Retries Overload a Recovering Dependency: ` +
|
|
172
|
+
'If automatic retries are declared, what prevents queued work from retrying simultaneously when the dependency recovers?',
|
|
187
173
|
probability: 'medium',
|
|
188
174
|
impact: 'high',
|
|
189
175
|
currentHandling: 'Retry without jitter causes thundering herd: all retries fire simultaneously after backoff, ' +
|
|
190
176
|
'potentially re-overloading the service as it recovers.',
|
|
191
|
-
requiredHandling: '
|
|
192
|
-
'In HALF-OPEN state: limit to 1-3 probe requests, not all queued requests simultaneously. ' +
|
|
193
|
-
'If probes succeed, circuit closes gradually.',
|
|
177
|
+
requiredHandling: 'Define bounded retry, jitter, and recovery-probe behavior from the dependency contract, or confirm that automatic retries are not used.',
|
|
194
178
|
dataConsistency: 'Thundering herd can cause the recovering service to fail again immediately after recovery.',
|
|
195
|
-
userExperience: '
|
|
179
|
+
userExperience: 'Use the recovery feedback and maximum wait declared by the client contract.',
|
|
196
180
|
},
|
|
197
181
|
];
|
|
198
182
|
}
|
|
@@ -7,17 +7,15 @@ export function generateRateLimitBypassScenarios(spec) {
|
|
|
7
7
|
return [
|
|
8
8
|
{
|
|
9
9
|
scenario: `[${spec.id}] Rate Limit Bypass — Distributed Attack: ` +
|
|
10
|
-
'The rate limit is configured per-IP. An attacker uses
|
|
10
|
+
'The rate limit is configured per-IP. An attacker uses many independent sources ' +
|
|
11
11
|
'to bypass the per-IP limit. What is the effective global rate of requests the system receives? ' +
|
|
12
12
|
'Does the system have any IP-independent rate limiting (per-account, global circuit breaker) to handle this?',
|
|
13
13
|
probability: 'medium',
|
|
14
14
|
impact: 'high',
|
|
15
|
-
currentHandling: 'Per-IP rate limiting
|
|
16
|
-
requiredHandling: '
|
|
17
|
-
'For authentication endpoints: per-account lockout after N failed attempts (regardless of source IP). ' +
|
|
18
|
-
'Use Redis-backed distributed rate limiting for consistency across multiple instances.',
|
|
15
|
+
currentHandling: 'Per-IP rate limiting alone may be ineffective against distributed attacks.',
|
|
16
|
+
requiredHandling: 'Define enforcement dimensions, shared-state boundaries, and rejection behavior from the rate-limit contract.',
|
|
19
17
|
dataConsistency: 'In-memory rate limiting (local to one instance) allows the effective limit to be multiplied by the number of instances.',
|
|
20
|
-
userExperience: '
|
|
18
|
+
userExperience: 'Use the retry guidance declared by the client contract.',
|
|
21
19
|
},
|
|
22
20
|
];
|
|
23
21
|
}
|
|
@@ -39,12 +37,12 @@ export function generateConcurrencyScenarios(spec, concurrencyModel) {
|
|
|
39
37
|
'Use sync/atomic for simple counters. ' +
|
|
40
38
|
'Always run `go test -race ./...` in CI to detect race conditions before production.',
|
|
41
39
|
dataConsistency: 'Race conditions on shared state lead to inconsistent reads and corrupted writes that are hard to reproduce.',
|
|
42
|
-
userExperience: '
|
|
40
|
+
userExperience: 'Use the failure response declared by the service contract.',
|
|
43
41
|
},
|
|
44
42
|
'async-await': {
|
|
45
43
|
scenario: `[${spec.id}] Concurrency — Event Loop Blocking: ` +
|
|
46
44
|
'Does this feature perform any synchronous CPU-intensive or I/O-blocking operation inside an async function? ' +
|
|
47
|
-
'Examples
|
|
45
|
+
'Examples include parsing payloads above the declared size envelope or synchronous cryptography. ' +
|
|
48
46
|
'RegExp with catastrophic backtracking. ' +
|
|
49
47
|
'These block the single-threaded event loop and degrade ALL concurrent requests, not just this one.',
|
|
50
48
|
probability: 'medium',
|
|
@@ -66,10 +64,10 @@ export function generateConcurrencyScenarios(spec, concurrencyModel) {
|
|
|
66
64
|
impact: 'critical',
|
|
67
65
|
currentHandling: 'Inconsistent lock ordering causes deadlocks that hang threads indefinitely, eventually exhausting the thread pool.',
|
|
68
66
|
requiredHandling: 'Establish a canonical lock ordering (always A before B, never B before A). Document this ordering. ' +
|
|
69
|
-
'Use
|
|
67
|
+
'Use bounded lock acquisition in tests with the timeout declared by the concurrency contract. ' +
|
|
70
68
|
'Consider using ReentrantLock over synchronized blocks for more control.',
|
|
71
69
|
dataConsistency: 'Deadlocked threads hold locks, preventing other threads from accessing the protected resources.',
|
|
72
|
-
userExperience: '
|
|
70
|
+
userExperience: 'Use the timeout response declared by the service contract.',
|
|
73
71
|
},
|
|
74
72
|
'rust-ownership': {
|
|
75
73
|
scenario: `[${spec.id}] Concurrency — Rust Logical Deadlock: ` +
|
|
@@ -95,9 +93,7 @@ export function generateConcurrencyScenarios(spec, concurrencyModel) {
|
|
|
95
93
|
probability: 'medium',
|
|
96
94
|
impact: 'high',
|
|
97
95
|
currentHandling: 'Unbounded mailboxes cause memory exhaustion under load. Message drops without metrics cause silent data loss.',
|
|
98
|
-
requiredHandling: '
|
|
99
|
-
'Emit a metric when mailbox exceeds 80% capacity. ' +
|
|
100
|
-
'Consider back-pressure: if the producer is too fast, slow it down at the source instead of dropping at the destination.',
|
|
96
|
+
requiredHandling: 'Define mailbox bounds, overflow behavior, and observability from the actor contract.',
|
|
101
97
|
dataConsistency: 'Dropped messages cause data loss that is not recoverable without event sourcing or DLQ.',
|
|
102
98
|
userExperience: 'Mailbox overflow is invisible to users until they notice missing data or failed operations.',
|
|
103
99
|
},
|
|
@@ -113,9 +109,9 @@ export function generateConcurrencyScenarios(spec, concurrencyModel) {
|
|
|
113
109
|
requiredHandling: 'Choose the backpressure strategy explicitly based on business requirements. ' +
|
|
114
110
|
'For critical events: use ERROR or bounded BUFFER to prevent loss. ' +
|
|
115
111
|
'For metrics/logging: DROP or LATEST is acceptable. ' +
|
|
116
|
-
'Test
|
|
112
|
+
'Test above the producer/subscriber rate envelope declared by the stream contract.',
|
|
117
113
|
dataConsistency: 'Backpressure strategy determines whether data loss is acceptable or whether the stream must be slowed down.',
|
|
118
|
-
userExperience: '
|
|
114
|
+
userExperience: 'Use the overflow response declared by the stream contract.',
|
|
119
115
|
},
|
|
120
116
|
};
|
|
121
117
|
const scenario = modelScenarios[concurrencyModel];
|
|
@@ -127,9 +123,9 @@ export function generateConcurrencyScenarios(spec, concurrencyModel) {
|
|
|
127
123
|
export function generateSagaFailureScenarios(spec) {
|
|
128
124
|
return [
|
|
129
125
|
{
|
|
130
|
-
scenario: `[${spec.id}] Distributed Saga — Partial Failure at Step
|
|
131
|
-
'If
|
|
132
|
-
'how long
|
|
126
|
+
scenario: `[${spec.id}] Distributed Saga — Partial Failure at a Later Step: ` +
|
|
127
|
+
'If a later saga step fails after earlier external actions completed, ' +
|
|
128
|
+
'how long may compensation take according to the saga contract? ' +
|
|
133
129
|
'How is it verified that the system is in a consistent state after compensation, ' +
|
|
134
130
|
'rather than in a partially-reverted state (step 1 rolled back but step 2 not yet)?',
|
|
135
131
|
probability: 'medium',
|
|
@@ -138,8 +134,7 @@ export function generateSagaFailureScenarios(spec) {
|
|
|
138
134
|
'(some external services have committed while others have not).',
|
|
139
135
|
requiredHandling: 'Each saga step must have a defined compensation operation. ' +
|
|
140
136
|
'Compensation must be idempotent (can be retried safely). ' +
|
|
141
|
-
'
|
|
142
|
-
'Add a saga status endpoint: GET /sagas/{id} returns the current step, status, and any failed compensations.',
|
|
137
|
+
'Persist enough saga state to resume from the last known step and expose status only through the interface declared by the contract.',
|
|
143
138
|
dataConsistency: 'Partial compensation leaves distributed state inconsistent. The saga log must be the source of truth for rollback status.',
|
|
144
139
|
userExperience: 'Users must receive a clear error explaining that the operation was rolled back and what state the system is in. ' +
|
|
145
140
|
'If rollback also failed, the user must be told that manual resolution is required.',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChallengeSpecFocus, FailureScenario, ProjectKnowledge, Spec } from '../../types/index.js';
|
|
2
|
+
import { type ChallengeCapabilities } from './scenarios-utils.js';
|
|
3
|
+
interface ScenarioCollectionInput {
|
|
4
|
+
spec: Spec;
|
|
5
|
+
specContent: string;
|
|
6
|
+
knowledge: ProjectKnowledge;
|
|
7
|
+
focusAreas: ChallengeSpecFocus[];
|
|
8
|
+
capabilities: ChallengeCapabilities;
|
|
9
|
+
}
|
|
10
|
+
/** Select scenario families before generation so unsupported templates never reach risk scoring. */
|
|
11
|
+
export declare function collectCapabilityScenarios(input: ScenarioCollectionInput): FailureScenario[];
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=scenario-collector.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { generateDataConsistencyScenarios, generateDesktopAutoUpdateChallenges, generateFailureScenarios, generateGameChallenges, generateScaleScenarios, generateSecurityScenarios, } from '../challenge-spec-scenarios.js';
|
|
2
|
+
import { generateAgentChallengeScenarios, isAgentSpec } from './agent-challenge-scenarios.js';
|
|
3
|
+
import { generateDddChallenges, generateXpPairClarityChallenge, } from './architecture-challenge-scenarios.js';
|
|
4
|
+
import { generateEventChallengeScenarios } from './event-challenge-scenarios.js';
|
|
5
|
+
import { generateImplementationContractChallengeScenarios } from './implementation-contract-challenge-scenarios.js';
|
|
6
|
+
import { getPlatformChallenges } from './platform-challenge-scenarios.js';
|
|
7
|
+
import { generatePrivacyChallengeScenarios } from './privacy-challenge-scenarios.js';
|
|
8
|
+
import { generateResilienceChallengeScenarios } from './resilience-challenge-scenarios.js';
|
|
9
|
+
import { generateSecurityChallengeScenarios } from './security-challenge-scenarios.js';
|
|
10
|
+
import { hasAnyChallengeCapability } from './scenarios-utils.js';
|
|
11
|
+
/** Select scenario families before generation so unsupported templates never reach risk scoring. */
|
|
12
|
+
export function collectCapabilityScenarios(input) {
|
|
13
|
+
const { spec, specContent, knowledge, focusAreas, capabilities } = input;
|
|
14
|
+
const scenarios = [];
|
|
15
|
+
const hasFocus = (focus) => focusAreas.includes(focus);
|
|
16
|
+
const hasCapability = (names) => hasAnyChallengeCapability(capabilities, names);
|
|
17
|
+
if (hasFocus('failures') &&
|
|
18
|
+
hasCapability([
|
|
19
|
+
'networkApi',
|
|
20
|
+
'database',
|
|
21
|
+
'userInput',
|
|
22
|
+
'externalService',
|
|
23
|
+
'payment',
|
|
24
|
+
'fileTransfer',
|
|
25
|
+
])) {
|
|
26
|
+
scenarios.push(...generateFailureScenarios(spec, specContent, knowledge));
|
|
27
|
+
}
|
|
28
|
+
if (hasFocus('security') &&
|
|
29
|
+
hasCapability(['authentication', 'authorization', 'userInput', 'database', 'payment'])) {
|
|
30
|
+
scenarios.push(...generateSecurityScenarios(spec, specContent, knowledge));
|
|
31
|
+
}
|
|
32
|
+
if (hasFocus('scale') && capabilities.scale) {
|
|
33
|
+
scenarios.push(...generateScaleScenarios(spec, specContent, knowledge));
|
|
34
|
+
}
|
|
35
|
+
if (hasFocus('data-consistency') && hasCapability(['dataWrite', 'cache', 'duplicateOperation'])) {
|
|
36
|
+
scenarios.push(...generateDataConsistencyScenarios(spec, specContent, knowledge));
|
|
37
|
+
}
|
|
38
|
+
const isGameProject = knowledge.projectCategory === 'game' ||
|
|
39
|
+
(knowledge.framework !== null &&
|
|
40
|
+
['unity', 'godot', 'unreal', 'bevy', 'phaser', 'pygame'].includes(knowledge.framework));
|
|
41
|
+
if (isGameProject) {
|
|
42
|
+
scenarios.push(...generateGameChallenges(spec, specContent, knowledge));
|
|
43
|
+
}
|
|
44
|
+
if (capabilities.desktopUpdate) {
|
|
45
|
+
scenarios.push(...generateDesktopAutoUpdateChallenges(spec, specContent, knowledge));
|
|
46
|
+
}
|
|
47
|
+
if (capabilities.agent && isAgentSpec(spec, specContent)) {
|
|
48
|
+
scenarios.push(...generateAgentChallengeScenarios(spec, specContent, knowledge));
|
|
49
|
+
}
|
|
50
|
+
if (hasFocus('failures') && capabilities.implementationContract) {
|
|
51
|
+
scenarios.push(...generateImplementationContractChallengeScenarios(spec, specContent));
|
|
52
|
+
}
|
|
53
|
+
if (capabilities.platform) {
|
|
54
|
+
scenarios.push(...getPlatformChallenges(spec, specContent, knowledge));
|
|
55
|
+
}
|
|
56
|
+
if (hasFocus('security') &&
|
|
57
|
+
hasCapability(['authentication', 'authorization', 'database', 'payment'])) {
|
|
58
|
+
scenarios.push(...generateSecurityChallengeScenarios(spec, specContent, knowledge));
|
|
59
|
+
}
|
|
60
|
+
if (capabilities.privacy) {
|
|
61
|
+
scenarios.push(...generatePrivacyChallengeScenarios(spec, specContent, knowledge));
|
|
62
|
+
}
|
|
63
|
+
if (capabilities.events) {
|
|
64
|
+
scenarios.push(...generateEventChallengeScenarios(spec, specContent, knowledge));
|
|
65
|
+
}
|
|
66
|
+
const hasDdd = knowledge.architecture.primary === 'ddd' || knowledge.architecture.secondary.includes('ddd');
|
|
67
|
+
if (hasDdd && capabilities.ddd) {
|
|
68
|
+
scenarios.push(...generateDddChallenges(spec, specContent));
|
|
69
|
+
}
|
|
70
|
+
if (hasCapability([
|
|
71
|
+
'externalService',
|
|
72
|
+
'networkApi',
|
|
73
|
+
'validation',
|
|
74
|
+
'rateLimit',
|
|
75
|
+
'concurrency',
|
|
76
|
+
'saga',
|
|
77
|
+
])) {
|
|
78
|
+
scenarios.push(...generateResilienceChallengeScenarios(spec, specContent, knowledge));
|
|
79
|
+
}
|
|
80
|
+
const agileConfig = knowledge.agileConfig;
|
|
81
|
+
if (agileConfig?.process === 'xp' && capabilities.xp) {
|
|
82
|
+
scenarios.push(...generateXpPairClarityChallenge(spec, specContent));
|
|
83
|
+
}
|
|
84
|
+
return scenarios;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=scenario-collector.js.map
|
|
@@ -7,6 +7,7 @@ const WRITE_SIGNALS = [
|
|
|
7
7
|
const CACHE_SIGNALS = [/\b(cache|real-time|realtime|concurrent|websocket|stale data)\b/i];
|
|
8
8
|
const DUPLICATE_OPERATION_SIGNALS = [
|
|
9
9
|
/\b(submit|form|checkout|order|payment|create record|mutation|button click)\b/i,
|
|
10
|
+
/\bduplicate\s+(?:request|submission|operation|record)\b/i,
|
|
10
11
|
];
|
|
11
12
|
export function generateDataConsistencyScenarios(_spec, content, _knowledge) {
|
|
12
13
|
const scenarios = [];
|
|
@@ -20,9 +21,9 @@ export function generateDataConsistencyScenarios(_spec, content, _knowledge) {
|
|
|
20
21
|
currentHandling: contentMentions(lower, ['transaction', 'atomic', 'rollback'])
|
|
21
22
|
? 'Transactions mentioned'
|
|
22
23
|
: 'Not addressed',
|
|
23
|
-
requiredHandling: '
|
|
24
|
-
dataConsistency: '
|
|
25
|
-
userExperience: '
|
|
24
|
+
requiredHandling: 'Define atomicity, rollback, and partial-failure behavior for the declared write boundary.',
|
|
25
|
+
dataConsistency: 'The write contract must state which states are valid after interruption.',
|
|
26
|
+
userExperience: 'Use the success and failure behavior declared by the interaction contract.',
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
// Stale data
|
|
@@ -34,9 +35,9 @@ export function generateDataConsistencyScenarios(_spec, content, _knowledge) {
|
|
|
34
35
|
currentHandling: contentMentions(lower, ['invalidat', 'ttl', 'websocket', 'real-time'])
|
|
35
36
|
? 'Some cache strategy mentioned'
|
|
36
37
|
: 'Not addressed',
|
|
37
|
-
requiredHandling: '
|
|
38
|
-
dataConsistency: 'Define
|
|
39
|
-
userExperience: '
|
|
38
|
+
requiredHandling: 'Define invalidation and freshness behavior from the declared cache contract.',
|
|
39
|
+
dataConsistency: 'Define and verify the acceptable staleness boundary.',
|
|
40
|
+
userExperience: 'Use the freshness feedback declared by the interaction contract.',
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
43
|
// Duplicate operations only apply to explicit user-triggered mutations.
|
|
@@ -48,9 +49,9 @@ export function generateDataConsistencyScenarios(_spec, content, _knowledge) {
|
|
|
48
49
|
currentHandling: contentMentions(lower, ['idempoten', 'debounce', 'disable', 'lock'])
|
|
49
50
|
? 'Some prevention mentioned'
|
|
50
51
|
: 'Not addressed',
|
|
51
|
-
requiredHandling: '
|
|
52
|
-
dataConsistency: '
|
|
53
|
-
userExperience: '
|
|
52
|
+
requiredHandling: 'Define duplicate-submission semantics and enforce them at the declared mutation boundary.',
|
|
53
|
+
dataConsistency: 'Repeated submissions must produce the result declared by the mutation contract.',
|
|
54
|
+
userExperience: 'Use the in-progress and completion feedback declared by the interaction contract.',
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
return scenarios;
|
|
@@ -1,101 +1,85 @@
|
|
|
1
1
|
// tools/challenge-spec/scenarios-failure.ts — Network, database, and resource failure scenarios
|
|
2
|
-
import { contentMentions } from './scenarios-utils.js';
|
|
2
|
+
import { contentMentions, detectChallengeCapabilities } from './scenarios-utils.js';
|
|
3
3
|
export function generateFailureScenarios(spec, content, _knowledge) {
|
|
4
4
|
const scenarios = [];
|
|
5
|
-
const
|
|
5
|
+
const capabilities = detectChallengeCapabilities(spec, content);
|
|
6
6
|
// Network failures
|
|
7
|
-
if (
|
|
8
|
-
spec.target === 'frontend' ||
|
|
9
|
-
lower.includes('api') ||
|
|
10
|
-
lower.includes('fetch') ||
|
|
11
|
-
lower.includes('request')) {
|
|
7
|
+
if (capabilities.networkApi) {
|
|
12
8
|
scenarios.push({
|
|
13
9
|
scenario: 'API endpoint becomes unreachable (network timeout)',
|
|
14
10
|
probability: 'medium',
|
|
15
11
|
impact: 'high',
|
|
16
|
-
currentHandling: contentMentions(
|
|
12
|
+
currentHandling: contentMentions(content, ['timeout', 'retry', 'fallback'])
|
|
17
13
|
? 'Partially addressed in spec'
|
|
18
14
|
: 'Not addressed in spec',
|
|
19
|
-
requiredHandling: '
|
|
15
|
+
requiredHandling: 'Define the request timeout, retry policy, and degraded behavior from the endpoint contract.',
|
|
20
16
|
dataConsistency: 'Ensure idempotent operations to prevent duplicate actions on retry',
|
|
21
|
-
userExperience: '
|
|
17
|
+
userExperience: 'Use the failure behavior defined by the client contract.',
|
|
22
18
|
});
|
|
23
19
|
}
|
|
24
20
|
// Database failures
|
|
25
|
-
if (
|
|
26
|
-
spec.target === 'fullstack' ||
|
|
27
|
-
spec.target === 'database' ||
|
|
28
|
-
lower.includes('database') ||
|
|
29
|
-
lower.includes('query') ||
|
|
30
|
-
lower.includes('schema')) {
|
|
21
|
+
if (capabilities.database) {
|
|
31
22
|
scenarios.push({
|
|
32
23
|
scenario: 'Database connection pool exhausted',
|
|
33
24
|
probability: 'low',
|
|
34
25
|
impact: 'critical',
|
|
35
26
|
currentHandling: 'Not addressed in spec',
|
|
36
|
-
requiredHandling: '
|
|
37
|
-
dataConsistency: '
|
|
38
|
-
userExperience: 'Return
|
|
27
|
+
requiredHandling: 'Define connection-pool bounds, exhaustion behavior, and recovery from the database contract.',
|
|
28
|
+
dataConsistency: 'Define how in-flight transactions remain consistent when capacity is exhausted.',
|
|
29
|
+
userExperience: 'Return the database-unavailable response defined by the service contract.',
|
|
39
30
|
});
|
|
40
31
|
scenarios.push({
|
|
41
32
|
scenario: 'Migration fails halfway through',
|
|
42
33
|
probability: 'medium',
|
|
43
34
|
impact: 'high',
|
|
44
|
-
currentHandling: contentMentions(
|
|
35
|
+
currentHandling: contentMentions(content, ['migration', 'rollback', 'reversible'])
|
|
45
36
|
? 'Partially addressed'
|
|
46
37
|
: 'Not addressed',
|
|
47
|
-
requiredHandling: '
|
|
48
|
-
dataConsistency: 'Partial schema changes
|
|
49
|
-
userExperience: '
|
|
38
|
+
requiredHandling: 'Define migration atomicity, recovery, rollback expectations, and verification evidence.',
|
|
39
|
+
dataConsistency: 'Partial schema changes must have an explicit recovery contract.',
|
|
40
|
+
userExperience: 'Use the availability behavior declared by the deployment contract.',
|
|
50
41
|
});
|
|
51
42
|
}
|
|
52
43
|
// Input validation failures
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
if (capabilities.userInput) {
|
|
45
|
+
scenarios.push({
|
|
46
|
+
scenario: 'Malformed or oversized input data',
|
|
47
|
+
probability: 'high',
|
|
48
|
+
impact: 'medium',
|
|
49
|
+
currentHandling: contentMentions(content, ['validat', 'sanitiz', 'schema', 'zod'])
|
|
50
|
+
? 'Validation mentioned in spec'
|
|
51
|
+
: 'Not explicitly addressed',
|
|
52
|
+
requiredHandling: 'Validate input at the declared boundary and define payload limits.',
|
|
53
|
+
dataConsistency: 'Invalid input must not reach the declared persistence boundary.',
|
|
54
|
+
userExperience: 'Return the validation feedback defined by the interaction contract.',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
64
57
|
// Third-party service failures
|
|
65
|
-
if (
|
|
66
|
-
lower.includes('payment') ||
|
|
67
|
-
lower.includes('email') ||
|
|
68
|
-
lower.includes('auth') ||
|
|
69
|
-
lower.includes('external') ||
|
|
70
|
-
lower.includes('webhook')) {
|
|
58
|
+
if (capabilities.externalService || capabilities.payment) {
|
|
71
59
|
scenarios.push({
|
|
72
|
-
scenario: 'Third-party service
|
|
60
|
+
scenario: 'Third-party service declared by the spec becomes unavailable',
|
|
73
61
|
probability: 'medium',
|
|
74
62
|
impact: 'critical',
|
|
75
|
-
currentHandling: contentMentions(
|
|
63
|
+
currentHandling: contentMentions(content, ['fallback', 'queue', 'retry', 'circuit'])
|
|
76
64
|
? 'Partially addressed'
|
|
77
65
|
: 'Not addressed',
|
|
78
|
-
requiredHandling: '
|
|
79
|
-
dataConsistency: '
|
|
80
|
-
userExperience: '
|
|
66
|
+
requiredHandling: 'Define observable failure and recovery behavior from the declared dependency contract.',
|
|
67
|
+
dataConsistency: 'Preserve idempotence for any operation the spec marks as retryable.',
|
|
68
|
+
userExperience: 'Use the degraded behavior defined by the dependency contract.',
|
|
81
69
|
});
|
|
82
70
|
}
|
|
83
71
|
// Memory/resource exhaustion
|
|
84
|
-
if (
|
|
85
|
-
lower.includes('file') ||
|
|
86
|
-
lower.includes('stream') ||
|
|
87
|
-
lower.includes('batch') ||
|
|
88
|
-
lower.includes('bulk')) {
|
|
72
|
+
if (capabilities.fileTransfer) {
|
|
89
73
|
scenarios.push({
|
|
90
74
|
scenario: 'Memory exhaustion from large file upload or batch operation',
|
|
91
75
|
probability: 'medium',
|
|
92
76
|
impact: 'high',
|
|
93
|
-
currentHandling: contentMentions(
|
|
77
|
+
currentHandling: contentMentions(content, ['limit', 'stream', 'chunk', 'pagination'])
|
|
94
78
|
? 'Partially addressed'
|
|
95
79
|
: 'Not addressed',
|
|
96
|
-
requiredHandling: '
|
|
97
|
-
dataConsistency: '
|
|
98
|
-
userExperience: '
|
|
80
|
+
requiredHandling: 'Define resource bounds and processing behavior for over-limit files or batches.',
|
|
81
|
+
dataConsistency: 'Define the consistency guarantee for interrupted file or batch processing.',
|
|
82
|
+
userExperience: 'Use the progress and cancellation behavior declared by the interaction contract.',
|
|
99
83
|
});
|
|
100
84
|
}
|
|
101
85
|
return scenarios;
|