project-tiny-context-harness 0.8.16 → 0.8.17
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/README.md +31 -16
- package/assets/README.md +31 -16
- package/assets/README.zh-CN.md +29 -14
- package/assets/context_templates/screen-contract.md +16 -0
- package/assets/skills/context_development_engineer/SKILL.md +16 -30
- package/assets/skills/context_development_engineer/references/engineering-design-reasoning.md +93 -0
- package/assets/skills/context_surface_contract/SKILL.md +13 -2
- package/assets/skills/context_uiux_design/SKILL.md +7 -3
- package/assets/skills/context_uiux_design/references/task-uiux-analysis.md +82 -0
- package/assets/skills/design-resource-authoring/SKILL.md +14 -8
- package/assets/skills/design-resource-authoring/references/downstream-handoff.md +18 -6
- package/assets/skills/design-resource-authoring/references/formal-selected-web-app-handoff.md +26 -8
- package/assets/skills/design-resource-authoring/references/implementation-feasibility.md +111 -0
- package/assets/skills/design-resource-authoring/references/open-design-provider.md +45 -7
- package/assets/skills/design-resource-authoring/references/recovery-and-writeback.md +7 -1
- package/assets/skills/design-resource-authoring/references/resource-selection.md +115 -3
- package/dist/commands/design-resource.js +9 -2
- package/dist/index.d.ts +1 -1
- package/dist/lib/design-resource-handoff-bundle.js +5 -0
- package/dist/lib/design-resource-handoff-input-types.d.ts +1 -1
- package/dist/lib/design-resource-handoff-manifest-projection.js +1 -0
- package/dist/lib/design-resource-handoff-set-integrity.js +1 -0
- package/dist/lib/design-resource-handoff-shape.js +6 -2
- package/dist/lib/design-resource-handoff-types.d.ts +5 -0
- package/dist/lib/design-resource-handoff-validation.js +12 -0
- package/dist/lib/design-resource-implementation-feasibility-model.d.ts +35 -0
- package/dist/lib/design-resource-implementation-feasibility-model.js +62 -0
- package/dist/lib/design-resource-implementation-feasibility-shape-sections.d.ts +43 -0
- package/dist/lib/design-resource-implementation-feasibility-shape-sections.js +142 -0
- package/dist/lib/design-resource-implementation-feasibility-shape.d.ts +4 -0
- package/dist/lib/design-resource-implementation-feasibility-shape.js +154 -0
- package/dist/lib/design-resource-implementation-feasibility-source-decision-projection.d.ts +24 -0
- package/dist/lib/design-resource-implementation-feasibility-source-decision-projection.js +106 -0
- package/dist/lib/design-resource-implementation-feasibility-source-decision.d.ts +21 -0
- package/dist/lib/design-resource-implementation-feasibility-source-decision.js +75 -0
- package/dist/lib/design-resource-implementation-feasibility-types.d.ts +135 -0
- package/dist/lib/design-resource-implementation-feasibility-types.js +37 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-cells.d.ts +5 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-cells.js +125 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-document.d.ts +4 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-document.js +167 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-facts.d.ts +3 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-facts.js +76 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-realizations.d.ts +8 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-realizations.js +76 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-support.d.ts +10 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-support.js +75 -0
- package/dist/lib/design-resource-implementation-feasibility-validation.d.ts +3 -0
- package/dist/lib/design-resource-implementation-feasibility-validation.js +138 -0
- package/dist/lib/design-resource-symbolic-fact-shape.js +3 -1
- package/dist/lib/design-resource-symbolic-fact-types.d.ts +5 -0
- package/dist/lib/design-resource-symbolic-fact-validation.js +13 -0
- package/dist/lib/long-task-activation-validation.js +77 -7
- package/dist/lib/long-task-check-runner.js +3 -296
- package/dist/lib/long-task-command-process.d.ts +9 -0
- package/dist/lib/long-task-command-process.js +175 -0
- package/dist/lib/long-task-delivery-validation.d.ts +5 -2
- package/dist/lib/long-task-delivery-validation.js +4 -4
- package/dist/lib/long-task-design-feasibility-binding-owners.d.ts +6 -0
- package/dist/lib/long-task-design-feasibility-binding-owners.js +52 -0
- package/dist/lib/long-task-design-feasibility-binding.d.ts +13 -0
- package/dist/lib/long-task-design-feasibility-binding.js +114 -0
- package/dist/lib/long-task-design-feasibility-source-closure.d.ts +11 -0
- package/dist/lib/long-task-design-feasibility-source-closure.js +78 -0
- package/dist/lib/long-task-design-resource-handoff.d.ts +3 -3
- package/dist/lib/long-task-design-resource-handoff.js +39 -3
- package/dist/lib/long-task-design-resource-method-binding.js +8 -2
- package/dist/lib/long-task-freshness.js +1 -1
- package/dist/lib/long-task-process-table.d.ts +12 -0
- package/dist/lib/long-task-process-table.js +119 -0
- package/dist/lib/long-task-process-tree.d.ts +9 -0
- package/dist/lib/long-task-process-tree.js +138 -0
- package/dist/lib/long-task-protected-files.d.ts +1 -1
- package/dist/lib/long-task-protected-files.js +1 -1
- package/dist/lib/long-task-ui-surface-policy.d.ts +2 -1
- package/dist/lib/long-task-ui-surface-policy.js +7 -4
- package/dist/lib/long-task-ui-surface-validation.d.ts +3 -1
- package/dist/lib/long-task-ui-surface-validation.js +2 -2
- package/dist/lib/repository-path-safety.d.ts +2 -0
- package/dist/lib/repository-path-safety.js +36 -0
- package/dist/public-types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { DESIGN_RESOURCE_FEASIBILITY_DECISION_SCHEMA, deriveDesignResourceFeasibilityConditionScopeSha256, parseDesignResourceFeasibilityDecisionProjections, } from "./design-resource-implementation-feasibility-source-decision.js";
|
|
2
|
+
import { createV1ImplementationFeasibilityTargetModel, createV2ImplementationFeasibilityTargetModel, } from "./design-resource-implementation-feasibility-model.js";
|
|
3
|
+
import { matchingFeasibilityBindingKeys, validateFeasibilityBindingOwnerRoots, } from "./long-task-design-feasibility-binding-owners.js";
|
|
4
|
+
import { validateFeasibilityBlockers, validateSelectedPlannedOwnerClaims, validateTechnicalDecisionClaim, } from "./long-task-design-feasibility-source-closure.js";
|
|
5
|
+
import { invalid } from "./long-task-design-resource-method-binding.js";
|
|
6
|
+
export function validateLongTaskDesignFeasibilityBindings(contract, contractTarget, preflight, sourceItems, options = {}) {
|
|
7
|
+
const document = preflight.technical_feasibility_documents.find((item) => item.target_ref === contractTarget.target.key);
|
|
8
|
+
if (!document) {
|
|
9
|
+
if (!contractTarget.binding.component_binding_refs.length)
|
|
10
|
+
invalid("componentless_surface_requires_blocker_only_feasibility", contractTarget.target.key);
|
|
11
|
+
return {
|
|
12
|
+
consumed_component_binding_refs: [
|
|
13
|
+
...contractTarget.binding.component_binding_refs,
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const model = feasibilityTargetModel(preflight, contractTarget.target.key);
|
|
18
|
+
const decisions = compiledDecisionSources(document, sourceItems);
|
|
19
|
+
const outcome = contract.outcomes.find((item) => item.key === contractTarget.outcome_key);
|
|
20
|
+
const bindingsByKey = new Map(outcome.technical.bindings.map((binding) => [binding.key, binding]));
|
|
21
|
+
const componentBindings = contractTarget.binding.component_binding_refs.map((bindingRef) => {
|
|
22
|
+
const binding = bindingsByKey.get(bindingRef);
|
|
23
|
+
if (!binding)
|
|
24
|
+
invalid("feasibility_component_binding_unknown", `${contractTarget.target.key}:${bindingRef}`);
|
|
25
|
+
return binding;
|
|
26
|
+
});
|
|
27
|
+
const routeBinding = bindingsByKey.get(contractTarget.binding.route_binding_ref);
|
|
28
|
+
if (!routeBinding)
|
|
29
|
+
invalid("feasibility_route_binding_unknown", `${contractTarget.target.key}:${contractTarget.binding.route_binding_ref}`);
|
|
30
|
+
const hasCandidateCells = document.component_family_cells.some((cell) => cell.feasible_realizations.length > 0);
|
|
31
|
+
const allCellsBlockerOnly = document.component_family_cells.length > 0 &&
|
|
32
|
+
document.component_family_cells.every((cell) => cell.feasible_realizations.length === 0 && cell.blocker_refs.length > 0);
|
|
33
|
+
if (!componentBindings.length && hasCandidateCells)
|
|
34
|
+
invalid("feasibility_component_binding_required", contractTarget.target.key);
|
|
35
|
+
if (!componentBindings.length && !allCellsBlockerOnly)
|
|
36
|
+
invalid("componentless_surface_requires_blocker_only_feasibility", contractTarget.target.key);
|
|
37
|
+
validateFeasibilityBindingOwnerRoots(document, componentBindings, routeBinding);
|
|
38
|
+
const consumedComponentBindings = new Set();
|
|
39
|
+
for (const cell of document.component_family_cells) {
|
|
40
|
+
const blockerOnly = cell.feasible_realizations.length === 0 && cell.blocker_refs.length > 0;
|
|
41
|
+
if (blockerOnly)
|
|
42
|
+
continue;
|
|
43
|
+
const matching = cell.feasible_realizations
|
|
44
|
+
.map((realization) => ({
|
|
45
|
+
realization,
|
|
46
|
+
bindingKeys: matchingFeasibilityBindingKeys(realization, componentBindings),
|
|
47
|
+
}))
|
|
48
|
+
.filter((candidate) => candidate.bindingKeys.size > 0);
|
|
49
|
+
const required = cell.required_realization.realization_ref;
|
|
50
|
+
if (required !== null &&
|
|
51
|
+
(matching.length !== 1 || matching[0].realization.key !== required))
|
|
52
|
+
invalid("feasibility_required_realization_binding_mismatch", `${cell.key}:${required}:${matching.map((item) => item.realization.key).join(",")}`);
|
|
53
|
+
if (required === null && matching.length === 0)
|
|
54
|
+
invalid("feasibility_realization_binding_missing", cell.key);
|
|
55
|
+
if (required === null && matching.length > 1)
|
|
56
|
+
invalid("feasibility_realization_binding_ambiguous", `${cell.key}:${matching.map((item) => item.realization.key).join(",")}`);
|
|
57
|
+
const selected = matching.length === 1 ? matching[0] : null;
|
|
58
|
+
if (selected)
|
|
59
|
+
for (const bindingKey of selected.bindingKeys)
|
|
60
|
+
consumedComponentBindings.add(bindingKey);
|
|
61
|
+
if (required !== null)
|
|
62
|
+
validateTechnicalDecisionClaim(contract, contractTarget, decisions, cell.required_realization.technical_authority_source_refs, {
|
|
63
|
+
schema_version: DESIGN_RESOURCE_FEASIBILITY_DECISION_SCHEMA,
|
|
64
|
+
mode: "required_realization",
|
|
65
|
+
target_ref: cell.target_ref,
|
|
66
|
+
component_family_ref: cell.component_family_ref,
|
|
67
|
+
condition_scope_sha256: deriveDesignResourceFeasibilityConditionScopeSha256(document, model, cell.condition_profile_ref),
|
|
68
|
+
realization_ref: required,
|
|
69
|
+
}, `required_realization:${cell.key}`);
|
|
70
|
+
if (selected)
|
|
71
|
+
validateSelectedPlannedOwnerClaims(contract, contractTarget, document, model, decisions, selected.realization, componentBindings, cell);
|
|
72
|
+
}
|
|
73
|
+
if (!options.deferComponentBindingAttribution)
|
|
74
|
+
validateComponentBindingAttribution(contractTarget, consumedComponentBindings);
|
|
75
|
+
validateFeasibilityBlockers(contract, contractTarget, document, model, decisions);
|
|
76
|
+
return {
|
|
77
|
+
consumed_component_binding_refs: [...consumedComponentBindings].sort(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function validateComponentBindingAttribution(contractTarget, consumedComponentBindings) {
|
|
81
|
+
for (const bindingRef of contractTarget.binding.component_binding_refs)
|
|
82
|
+
if (!consumedComponentBindings.has(bindingRef))
|
|
83
|
+
invalid("feasibility_component_binding_unattributed", `${contractTarget.target.key}:${bindingRef}`);
|
|
84
|
+
}
|
|
85
|
+
function feasibilityTargetModel(preflight, targetRef) {
|
|
86
|
+
if ("preflight_schema_version" in preflight) {
|
|
87
|
+
const target = preflight.handoff.targets.find((candidate) => candidate.key === targetRef);
|
|
88
|
+
return createV2ImplementationFeasibilityTargetModel(target, preflight.manifest);
|
|
89
|
+
}
|
|
90
|
+
const target = preflight.handoff.targets.find((candidate) => candidate.key === targetRef);
|
|
91
|
+
return createV1ImplementationFeasibilityTargetModel(preflight.handoff, target);
|
|
92
|
+
}
|
|
93
|
+
function compiledDecisionSources(document, sourceItems) {
|
|
94
|
+
const index = new Map();
|
|
95
|
+
for (const record of document.source_records) {
|
|
96
|
+
if (record.locator.kind !== "source_item")
|
|
97
|
+
continue;
|
|
98
|
+
const item = sourceItems.find((candidate) => candidate.source_path === record.path &&
|
|
99
|
+
candidate.key === record.locator.value);
|
|
100
|
+
if (!item)
|
|
101
|
+
invalid("feasibility_source_item_not_declared", `${record.key}:${record.path}#${record.locator.value}`);
|
|
102
|
+
if (item.text_sha256 !== record.locator.text_sha256)
|
|
103
|
+
invalid("feasibility_source_item_digest_mismatch", `${record.key}:${record.locator.text_sha256}:${item.text_sha256}`);
|
|
104
|
+
index.set(record.key, {
|
|
105
|
+
source_record_ref: record.key,
|
|
106
|
+
source_path: record.path,
|
|
107
|
+
source_item_key: item.key,
|
|
108
|
+
source_item_kind: item.kind,
|
|
109
|
+
text_sha256: item.text_sha256,
|
|
110
|
+
projections: parseDesignResourceFeasibilityDecisionProjections(record.path, item.key, item.normalized_text),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return index;
|
|
114
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DesignResourceImplementationFeasibilityTargetModel } from "./design-resource-implementation-feasibility-model.js";
|
|
2
|
+
import { type DesignResourceFeasibilityDecisionProjection, type DesignResourceFeasibilityDecisionSourceIndex } from "./design-resource-implementation-feasibility-source-decision.js";
|
|
3
|
+
import type { DesignResourceImplementationFeasibilityV1 } from "./design-resource-implementation-feasibility-types.js";
|
|
4
|
+
import type { DeliveryBindingV2, DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
5
|
+
import type { ContractDesignTarget } from "./long-task-design-resource-handoff.js";
|
|
6
|
+
type FeasibilityCell = DesignResourceImplementationFeasibilityV1["component_family_cells"][number];
|
|
7
|
+
type FeasibleRealization = FeasibilityCell["feasible_realizations"][number];
|
|
8
|
+
export declare function validateSelectedPlannedOwnerClaims(contract: DeliveryContractV2, contractTarget: ContractDesignTarget, document: DesignResourceImplementationFeasibilityV1, model: DesignResourceImplementationFeasibilityTargetModel, decisions: DesignResourceFeasibilityDecisionSourceIndex, realization: FeasibleRealization, bindings: DeliveryBindingV2[], cell: FeasibilityCell): void;
|
|
9
|
+
export declare function validateTechnicalDecisionClaim(contract: DeliveryContractV2, contractTarget: ContractDesignTarget, decisions: DesignResourceFeasibilityDecisionSourceIndex, sourceRefs: string[], expectation: DesignResourceFeasibilityDecisionProjection, label: string): void;
|
|
10
|
+
export declare function validateFeasibilityBlockers(contract: DeliveryContractV2, contractTarget: ContractDesignTarget, document: DesignResourceImplementationFeasibilityV1, model: DesignResourceImplementationFeasibilityTargetModel, decisions: DesignResourceFeasibilityDecisionSourceIndex): void;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { DESIGN_RESOURCE_FEASIBILITY_DECISION_SCHEMA, deriveDesignResourceFeasibilityConditionScopeSha256, requireExactFeasibilityDecisionProjection, } from "./design-resource-implementation-feasibility-source-decision.js";
|
|
2
|
+
import { invalid } from "./long-task-design-resource-method-binding.js";
|
|
3
|
+
export function validateSelectedPlannedOwnerClaims(contract, contractTarget, document, model, decisions, realization, bindings, cell) {
|
|
4
|
+
for (const owner of realization.owner_candidates) {
|
|
5
|
+
if (owner.kind !== "planned_logical_owner")
|
|
6
|
+
continue;
|
|
7
|
+
if (!bindings.some((binding) => binding.existence === "planned" &&
|
|
8
|
+
(binding.key === owner.locator || binding.target === owner.locator)))
|
|
9
|
+
continue;
|
|
10
|
+
validateTechnicalDecisionClaim(contract, contractTarget, decisions, owner.authorization_source_refs, {
|
|
11
|
+
schema_version: DESIGN_RESOURCE_FEASIBILITY_DECISION_SCHEMA,
|
|
12
|
+
mode: "planned_owner_authorization",
|
|
13
|
+
target_ref: cell.target_ref,
|
|
14
|
+
component_family_ref: cell.component_family_ref,
|
|
15
|
+
condition_scope_sha256: deriveDesignResourceFeasibilityConditionScopeSha256(document, model, cell.condition_profile_ref),
|
|
16
|
+
owner_locator: owner.locator,
|
|
17
|
+
}, `planned_owner:${cell.key}:${owner.locator}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function validateTechnicalDecisionClaim(contract, contractTarget, decisions, sourceRefs, expectation, label) {
|
|
21
|
+
const source = requireDecisionSource(sourceRefs, decisions, expectation, label, ["technical_obligation"], true);
|
|
22
|
+
const claim = exactSourceClaim(contract, source, label);
|
|
23
|
+
const validOutcomeClaim = claim.disposition.type === "claim" &&
|
|
24
|
+
claim.disposition.refs.some((ref) => ref.startsWith(`${contractTarget.outcome_key}.`));
|
|
25
|
+
if (!validOutcomeClaim && claim.disposition.type !== "global_constraint")
|
|
26
|
+
invalid("feasibility_technical_decision_claim_disposition_invalid", `${label}:${claim.key}:${claim.disposition.type}`);
|
|
27
|
+
}
|
|
28
|
+
export function validateFeasibilityBlockers(contract, contractTarget, document, model, decisions) {
|
|
29
|
+
for (const blocker of document.blockers) {
|
|
30
|
+
const source = requireDecisionSource(blocker.source_record_refs, decisions, {
|
|
31
|
+
schema_version: DESIGN_RESOURCE_FEASIBILITY_DECISION_SCHEMA,
|
|
32
|
+
mode: "feasibility_blocker",
|
|
33
|
+
target_ref: blocker.target_ref,
|
|
34
|
+
component_family_ref: blocker.component_family_ref,
|
|
35
|
+
condition_scope_sha256: deriveDesignResourceFeasibilityConditionScopeSha256(document, model, blocker.condition_profile_ref),
|
|
36
|
+
blocker_ref: blocker.key,
|
|
37
|
+
substrate_observation_refs: blocker.substrate_observation_refs,
|
|
38
|
+
}, `blocker:${blocker.key}`, ["decision", "external_confirmation"], false);
|
|
39
|
+
const claim = exactSourceClaim(contract, source, `blocker:${blocker.key}`);
|
|
40
|
+
if (source.source_item_kind === "decision") {
|
|
41
|
+
if (claim.disposition.type !== "decision_required")
|
|
42
|
+
invalid("feasibility_blocker_decision_required", `${blocker.key}:${claim.key}:${claim.disposition.type}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (claim.disposition.type !== "external_confirmation")
|
|
46
|
+
invalid("feasibility_blocker_external_confirmation_required", `${blocker.key}:${claim.key}:${claim.disposition.type}`);
|
|
47
|
+
const impactedTargetClaims = new Set(contractTarget.target.claim_refs.map((claimRef) => `${contractTarget.outcome_key}.${claimRef}`));
|
|
48
|
+
for (const confirmationRef of claim.disposition.refs) {
|
|
49
|
+
const confirmation = contract.global.acceptance.external_confirmations.find((candidate) => candidate.key === confirmationRef);
|
|
50
|
+
if (!confirmation)
|
|
51
|
+
invalid("feasibility_blocker_confirmation_unknown", `${blocker.key}:${confirmationRef}`);
|
|
52
|
+
if (!confirmation.blocks_target)
|
|
53
|
+
invalid("feasibility_blocker_confirmation_not_blocking", `${blocker.key}:${confirmationRef}`);
|
|
54
|
+
if (!confirmation.impact_claims.some((claimRef) => impactedTargetClaims.has(claimRef)))
|
|
55
|
+
invalid("feasibility_blocker_confirmation_target_claim_missing", `${blocker.key}:${confirmationRef}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function requireDecisionSource(sourceRefs, decisions, expectation, label, allowedItemKinds, allReferencesMustBeSourceItems) {
|
|
60
|
+
try {
|
|
61
|
+
return requireExactFeasibilityDecisionProjection(sourceRefs, decisions, expectation, label, { allReferencesMustBeSourceItems, allowedItemKinds });
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
invalid("feasibility_source_decision_invalid", `${label}:${message(error)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function exactSourceClaim(contract, source, label) {
|
|
68
|
+
if (!contract.task.source_paths.includes(source.source_path))
|
|
69
|
+
invalid("feasibility_authority_source_not_declared", `${label}:${source.source_path}`);
|
|
70
|
+
const sourceRef = `${source.source_path}#${source.source_item_key}`;
|
|
71
|
+
const matching = contract.source_claims.filter((claim) => claim.source_ref === sourceRef);
|
|
72
|
+
if (matching.length !== 1)
|
|
73
|
+
invalid("feasibility_authority_claim_count", `${label}:${sourceRef}:${matching.length}`);
|
|
74
|
+
return matching[0];
|
|
75
|
+
}
|
|
76
|
+
function message(error) {
|
|
77
|
+
return error instanceof Error ? error.message : String(error);
|
|
78
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DesignResourceHandoffPreflightV1, DesignResourceHandoffTargetV1 } from "./design-resource-handoff-types.js";
|
|
2
2
|
import type { DesignResourceHandoffPreflightV2 } from "./design-resource-symbolic-fact-types.js";
|
|
3
|
-
import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
3
|
+
import type { CompiledSourceItemV2, DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
4
4
|
export interface ContractDesignTarget {
|
|
5
5
|
outcome_key: string;
|
|
6
6
|
binding: DeliveryContractV2["outcomes"][number]["product"]["surface_bindings"][number];
|
|
@@ -14,5 +14,5 @@ export interface LongTaskDesignHandoffConsumer {
|
|
|
14
14
|
consume(preflight: DesignResourceHandoffPreflightV1 | DesignResourceHandoffPreflightV2): void;
|
|
15
15
|
finish(): void;
|
|
16
16
|
}
|
|
17
|
-
export declare function validateLongTaskDesignResourceHandoffs(contract: DeliveryContractV2, repository: string): Promise<void>;
|
|
18
|
-
export declare function createLongTaskDesignHandoffConsumer(contract: DeliveryContractV2): LongTaskDesignHandoffConsumer;
|
|
17
|
+
export declare function validateLongTaskDesignResourceHandoffs(contract: DeliveryContractV2, repository: string, sourceItems: CompiledSourceItemV2[]): Promise<void>;
|
|
18
|
+
export declare function createLongTaskDesignHandoffConsumer(contract: DeliveryContractV2, sourceItems?: CompiledSourceItemV2[]): LongTaskDesignHandoffConsumer;
|
|
@@ -6,8 +6,9 @@ import { preflightParsedDesignResourceHandoffAny } from "./design-resource-hando
|
|
|
6
6
|
import { assertSameSet, designSourceItemClaims, invalid, validateTargetIdentity, validateSymbolicTargetIdentity, validateSymbolicVerificationMethodBindings, validateVerificationMethodBindings, } from "./long-task-design-resource-method-binding.js";
|
|
7
7
|
import { validateLongTaskDesignTargetCapabilities } from "./long-task-design-target-capabilities.js";
|
|
8
8
|
import { assertProtectedRepositoryFile } from "./long-task-protected-files.js";
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import { validateLongTaskDesignFeasibilityBindings } from "./long-task-design-feasibility-binding.js";
|
|
10
|
+
export async function validateLongTaskDesignResourceHandoffs(contract, repository, sourceItems) {
|
|
11
|
+
const consumer = createLongTaskDesignHandoffConsumer(contract, sourceItems);
|
|
11
12
|
for (const sourcePath of contract.task.source_paths) {
|
|
12
13
|
if (!sourcePath.toLowerCase().endsWith(".md"))
|
|
13
14
|
continue;
|
|
@@ -19,7 +20,7 @@ export async function validateLongTaskDesignResourceHandoffs(contract, repositor
|
|
|
19
20
|
}
|
|
20
21
|
consumer.finish();
|
|
21
22
|
}
|
|
22
|
-
export function createLongTaskDesignHandoffConsumer(contract) {
|
|
23
|
+
export function createLongTaskDesignHandoffConsumer(contract, sourceItems = []) {
|
|
23
24
|
const contractTargets = contract.outcomes.flatMap((outcome) => outcome.product.surface_bindings.flatMap((binding) => binding.design_targets.map((target) => ({
|
|
24
25
|
outcome_key: outcome.key,
|
|
25
26
|
binding,
|
|
@@ -29,6 +30,7 @@ export function createLongTaskDesignHandoffConsumer(contract) {
|
|
|
29
30
|
const indexedTargetKeys = new Set();
|
|
30
31
|
const consumed = new Set();
|
|
31
32
|
const validationErrors = new Map();
|
|
33
|
+
const feasibilityBindingRefsBySurface = new Map();
|
|
32
34
|
let duplicate = null;
|
|
33
35
|
const unbound = [];
|
|
34
36
|
const handoffSetIntegrity = createDesignResourceHandoffSetIntegrity(invalid);
|
|
@@ -54,6 +56,14 @@ export function createLongTaskDesignHandoffConsumer(contract) {
|
|
|
54
56
|
else
|
|
55
57
|
resourcePaths.set(resource.path, identity);
|
|
56
58
|
}
|
|
59
|
+
for (const input of preflight.handoff.technical_feasibility_inputs) {
|
|
60
|
+
const identity = JSON.stringify(input);
|
|
61
|
+
const previous = resourcePaths.get(input.path);
|
|
62
|
+
if (previous && previous !== identity)
|
|
63
|
+
recordCrossVersionIssue("handoff_set_feasibility_path_conflict", input.path);
|
|
64
|
+
else
|
|
65
|
+
resourcePaths.set(input.path, identity);
|
|
66
|
+
}
|
|
57
67
|
if (!("preflight_schema_version" in preflight))
|
|
58
68
|
handoffSetIntegrity.consume(preflight);
|
|
59
69
|
for (const target of preflight.handoff.targets) {
|
|
@@ -75,6 +85,8 @@ export function createLongTaskDesignHandoffConsumer(contract) {
|
|
|
75
85
|
target,
|
|
76
86
|
};
|
|
77
87
|
validateSymbolicTargetIdentity(contractTarget, indexedTarget);
|
|
88
|
+
const feasibility = validateLongTaskDesignFeasibilityBindings(contract, contractTarget, preflight, sourceItems, { deferComponentBindingAttribution: true });
|
|
89
|
+
recordFeasibilityBindingAttribution(feasibilityBindingRefsBySurface, contractTarget, feasibility.consumed_component_binding_refs);
|
|
78
90
|
validateSymbolicCoverageClaims(contract, contractTarget, indexedTarget);
|
|
79
91
|
validateBlockerBindings(contract, contractTarget, indexedTarget);
|
|
80
92
|
}
|
|
@@ -84,6 +96,8 @@ export function createLongTaskDesignHandoffConsumer(contract) {
|
|
|
84
96
|
target: target,
|
|
85
97
|
};
|
|
86
98
|
validateTargetIdentity(contractTarget, indexedTarget);
|
|
99
|
+
const feasibility = validateLongTaskDesignFeasibilityBindings(contract, contractTarget, preflight, sourceItems, { deferComponentBindingAttribution: true });
|
|
100
|
+
recordFeasibilityBindingAttribution(feasibilityBindingRefsBySurface, contractTarget, feasibility.consumed_component_binding_refs);
|
|
87
101
|
validateLongTaskDesignTargetCapabilities(contract, contractTarget, indexedTarget);
|
|
88
102
|
validateCoverageClaims(contract, contractTarget, indexedTarget);
|
|
89
103
|
validateBlockerBindings(contract, contractTarget, indexedTarget);
|
|
@@ -106,11 +120,33 @@ export function createLongTaskDesignHandoffConsumer(contract) {
|
|
|
106
120
|
if (validationErrors.has(contractTarget.target.key))
|
|
107
121
|
throw validationErrors.get(contractTarget.target.key);
|
|
108
122
|
}
|
|
123
|
+
validateSurfaceFeasibilityBindingAttribution(contractTargets, feasibilityBindingRefsBySurface);
|
|
109
124
|
if (unbound.length)
|
|
110
125
|
invalid("handoff_target_unbound", unbound[0]);
|
|
111
126
|
},
|
|
112
127
|
};
|
|
113
128
|
}
|
|
129
|
+
function recordFeasibilityBindingAttribution(bySurface, contractTarget, bindingRefs) {
|
|
130
|
+
const key = surfaceBindingIdentity(contractTarget);
|
|
131
|
+
const consumed = bySurface.get(key) ?? new Set();
|
|
132
|
+
for (const bindingRef of bindingRefs)
|
|
133
|
+
consumed.add(bindingRef);
|
|
134
|
+
bySurface.set(key, consumed);
|
|
135
|
+
}
|
|
136
|
+
function validateSurfaceFeasibilityBindingAttribution(contractTargets, bySurface) {
|
|
137
|
+
const surfaces = new Map();
|
|
138
|
+
for (const contractTarget of contractTargets)
|
|
139
|
+
surfaces.set(surfaceBindingIdentity(contractTarget), contractTarget);
|
|
140
|
+
for (const [surfaceKey, contractTarget] of surfaces) {
|
|
141
|
+
const consumed = bySurface.get(surfaceKey) ?? new Set();
|
|
142
|
+
for (const bindingRef of contractTarget.binding.component_binding_refs)
|
|
143
|
+
if (!consumed.has(bindingRef))
|
|
144
|
+
invalid("feasibility_component_binding_unattributed", `${contractTarget.binding.key}:${bindingRef}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function surfaceBindingIdentity(contractTarget) {
|
|
148
|
+
return `${contractTarget.outcome_key}\0${contractTarget.binding.key}`;
|
|
149
|
+
}
|
|
114
150
|
function validateSymbolicCoverageClaims(contract, contractTarget, indexed) {
|
|
115
151
|
const claims = new Map(contract.source_claims.map((item) => [item.key, item]));
|
|
116
152
|
const rows = indexed.preflight.handoff.coverage.filter((row) => row.target_ref === contractTarget.target.key);
|
|
@@ -10,7 +10,10 @@ export function validateTargetIdentity(contractTarget, indexed) {
|
|
|
10
10
|
assertSameSet(target.condition_keys, handoffTarget.condition_refs, "target_conditions_mismatch", target.key);
|
|
11
11
|
const resourcePaths = handoffTarget.resource_refs.map((ref) => indexed.preflight.handoff.resources.find((item) => item.key === ref)
|
|
12
12
|
.path);
|
|
13
|
-
|
|
13
|
+
const feasibilityPaths = indexed.preflight.handoff.technical_feasibility_inputs
|
|
14
|
+
.filter((item) => item.target_ref === handoffTarget.key)
|
|
15
|
+
.map((item) => item.path);
|
|
16
|
+
assertSameSet(target.source_paths, [indexed.preflight.handoff_path, ...resourcePaths, ...feasibilityPaths], "target_source_paths_mismatch", target.key);
|
|
14
17
|
}
|
|
15
18
|
export function validateSymbolicTargetIdentity(contractTarget, indexed) {
|
|
16
19
|
const { target } = contractTarget;
|
|
@@ -25,7 +28,10 @@ export function validateSymbolicTargetIdentity(contractTarget, indexed) {
|
|
|
25
28
|
invalid("v2_ground_method_bindings_forbidden", target.key);
|
|
26
29
|
const resourcePaths = handoffTarget.resource_refs.map((ref) => indexed.preflight.handoff.resources.find((item) => item.key === ref)
|
|
27
30
|
.path);
|
|
28
|
-
|
|
31
|
+
const feasibilityPaths = indexed.preflight.handoff.technical_feasibility_inputs
|
|
32
|
+
.filter((item) => item.target_ref === handoffTarget.key)
|
|
33
|
+
.map((item) => item.path);
|
|
34
|
+
assertSameSet(target.source_paths, [indexed.preflight.handoff_path, ...resourcePaths, ...feasibilityPaths], "target_source_paths_mismatch", target.key);
|
|
29
35
|
}
|
|
30
36
|
export function validateSymbolicVerificationMethodBindings(contract, contractTarget, indexed, claimsBySourceItem) {
|
|
31
37
|
const target = contractTarget.target;
|
|
@@ -8,7 +8,7 @@ import { parseDeliveryContractBundle } from "./long-task-delivery-parser.js";
|
|
|
8
8
|
export async function deliveryCompileFreshness(compiled) {
|
|
9
9
|
const findings = [];
|
|
10
10
|
try {
|
|
11
|
-
const currentContract = await parseDeliveryContractBundle(compiled.workdir);
|
|
11
|
+
const currentContract = await parseDeliveryContractBundle(compiled.workdir, compiled.repository_root, { validate_structure: false });
|
|
12
12
|
if (sha256Hex(canonicalValueJson(currentContract.contract)) !==
|
|
13
13
|
compiled.contract_sha256)
|
|
14
14
|
findings.push(`contract_changed_after_compile:${compiled.contract_file}`);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ProcessInstance {
|
|
2
|
+
pid: number;
|
|
3
|
+
parent_pid: number;
|
|
4
|
+
start_identity: string | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function processInstanceMatches(expected: Pick<ProcessInstance, "pid" | "start_identity">, current: Pick<ProcessInstance, "pid" | "start_identity">): boolean;
|
|
7
|
+
export declare function processSnapshot(): Promise<ProcessInstance[]>;
|
|
8
|
+
export declare function descendantsOf(rootPid: number, snapshot: readonly ProcessInstance[]): ProcessInstance[];
|
|
9
|
+
export declare function liveProcessInstances(candidates: readonly ProcessInstance[], snapshot: readonly ProcessInstance[]): ProcessInstance[];
|
|
10
|
+
export declare function rootProcessAlive(rootPid: number, rootInstance: ProcessInstance | null, snapshot: readonly ProcessInstance[]): boolean;
|
|
11
|
+
export declare function uniqueProcessInstances(instances: readonly ProcessInstance[]): ProcessInstance[];
|
|
12
|
+
export declare function processInstanceKey(instance: ProcessInstance): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const PROCESS_TABLE_OUTPUT_LIMIT = 2 * 1024 * 1024;
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export function processInstanceMatches(expected, current) {
|
|
6
|
+
if (expected.pid !== current.pid)
|
|
7
|
+
return false;
|
|
8
|
+
if (expected.start_identity === null || current.start_identity === null)
|
|
9
|
+
return true;
|
|
10
|
+
return expected.start_identity === current.start_identity;
|
|
11
|
+
}
|
|
12
|
+
export async function processSnapshot() {
|
|
13
|
+
let stdout;
|
|
14
|
+
try {
|
|
15
|
+
const result = process.platform === "win32"
|
|
16
|
+
? await execFileAsync("powershell.exe", [
|
|
17
|
+
"-NoLogo",
|
|
18
|
+
"-NoProfile",
|
|
19
|
+
"-NonInteractive",
|
|
20
|
+
"-Command",
|
|
21
|
+
'$rows = Get-CimInstance Win32_Process; foreach ($row in $rows) { $started = if ($null -eq $row.CreationDate) { "unknown" } else { $row.CreationDate.ToUniversalTime().Ticks }; "$($row.ProcessId) $($row.ParentProcessId) $started" }',
|
|
22
|
+
], {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
windowsHide: true,
|
|
25
|
+
timeout: 5_000,
|
|
26
|
+
maxBuffer: PROCESS_TABLE_OUTPUT_LIMIT,
|
|
27
|
+
})
|
|
28
|
+
: await execFileAsync("ps", ["-A", "-o", "pid=,ppid="], {
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
timeout: 5_000,
|
|
31
|
+
maxBuffer: PROCESS_TABLE_OUTPUT_LIMIT,
|
|
32
|
+
});
|
|
33
|
+
stdout = result.stdout;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new Error(`process_observer_process_tree_inspection_unavailable:${message(error)}`);
|
|
37
|
+
}
|
|
38
|
+
const rows = [];
|
|
39
|
+
for (const line of stdout.split(/\r?\n/u)) {
|
|
40
|
+
const [pidText, parentText, startText] = line.trim().split(/\s+/u);
|
|
41
|
+
const pid = Number.parseInt(pidText ?? "", 10);
|
|
42
|
+
const parent = Number.parseInt(parentText ?? "", 10);
|
|
43
|
+
if (!Number.isSafeInteger(pid) || !Number.isSafeInteger(parent))
|
|
44
|
+
continue;
|
|
45
|
+
rows.push({
|
|
46
|
+
pid,
|
|
47
|
+
parent_pid: parent,
|
|
48
|
+
start_identity: process.platform === "win32" && startText ? startText : null,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return rows;
|
|
52
|
+
}
|
|
53
|
+
export function descendantsOf(rootPid, snapshot) {
|
|
54
|
+
const children = new Map();
|
|
55
|
+
for (const processInstance of snapshot) {
|
|
56
|
+
const row = children.get(processInstance.parent_pid);
|
|
57
|
+
if (row)
|
|
58
|
+
row.push(processInstance);
|
|
59
|
+
else
|
|
60
|
+
children.set(processInstance.parent_pid, [processInstance]);
|
|
61
|
+
}
|
|
62
|
+
const result = [];
|
|
63
|
+
const pending = [...(children.get(rootPid) ?? [])];
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
while (pending.length) {
|
|
66
|
+
const processInstance = pending.shift();
|
|
67
|
+
const key = processInstanceKey(processInstance);
|
|
68
|
+
if (seen.has(key))
|
|
69
|
+
continue;
|
|
70
|
+
seen.add(key);
|
|
71
|
+
result.push(processInstance);
|
|
72
|
+
pending.push(...(children.get(processInstance.pid) ?? []));
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
export function liveProcessInstances(candidates, snapshot) {
|
|
77
|
+
if (process.platform !== "win32")
|
|
78
|
+
return candidates.filter((candidate) => processIdExists(candidate.pid));
|
|
79
|
+
const currentByPid = new Map(snapshot.map((entry) => [entry.pid, entry]));
|
|
80
|
+
return candidates.filter((candidate) => {
|
|
81
|
+
const current = currentByPid.get(candidate.pid);
|
|
82
|
+
return current ? processInstanceMatches(candidate, current) : false;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export function rootProcessAlive(rootPid, rootInstance, snapshot) {
|
|
86
|
+
if (process.platform !== "win32")
|
|
87
|
+
return processIdExists(rootPid);
|
|
88
|
+
const current = snapshot.find((entry) => entry.pid === rootPid);
|
|
89
|
+
if (!current)
|
|
90
|
+
return false;
|
|
91
|
+
return rootInstance ? processInstanceMatches(rootInstance, current) : true;
|
|
92
|
+
}
|
|
93
|
+
export function uniqueProcessInstances(instances) {
|
|
94
|
+
return [
|
|
95
|
+
...new Map(instances.map((instance) => [processInstanceKey(instance), instance])).values(),
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
export function processInstanceKey(instance) {
|
|
99
|
+
return `${instance.pid}:${instance.start_identity ?? "pid-only"}`;
|
|
100
|
+
}
|
|
101
|
+
function processIdExists(pid) {
|
|
102
|
+
try {
|
|
103
|
+
process.kill(pid, 0);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (nodeErrorCode(error) === "ESRCH")
|
|
108
|
+
return false;
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function nodeErrorCode(error) {
|
|
113
|
+
return error && typeof error === "object" && "code" in error
|
|
114
|
+
? String(error.code)
|
|
115
|
+
: null;
|
|
116
|
+
}
|
|
117
|
+
function message(error) {
|
|
118
|
+
return error instanceof Error ? error.message : String(error);
|
|
119
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { processInstanceMatches } from "./long-task-process-table.js";
|
|
2
|
+
export interface ProcessTreeObserver {
|
|
3
|
+
stop(): void;
|
|
4
|
+
wait(): Promise<void>;
|
|
5
|
+
terminate(force: boolean): Promise<void>;
|
|
6
|
+
forceQuiescence(): Promise<void>;
|
|
7
|
+
assertQuiescent(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare function createProcessTreeObserver(rootPid: number): ProcessTreeObserver;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { descendantsOf, liveProcessInstances, processInstanceKey, processInstanceMatches, processSnapshot, rootProcessAlive, uniqueProcessInstances, } from "./long-task-process-table.js";
|
|
4
|
+
export { processInstanceMatches } from "./long-task-process-table.js";
|
|
5
|
+
const PROCESS_TREE_GRACE_MS = 1_000;
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
export function createProcessTreeObserver(rootPid) {
|
|
8
|
+
let stopped = false;
|
|
9
|
+
let rootInstance = null;
|
|
10
|
+
const observed = new Map();
|
|
11
|
+
const monitor = observe();
|
|
12
|
+
return {
|
|
13
|
+
stop() {
|
|
14
|
+
stopped = true;
|
|
15
|
+
},
|
|
16
|
+
wait() {
|
|
17
|
+
return monitor;
|
|
18
|
+
},
|
|
19
|
+
terminate(force) {
|
|
20
|
+
return terminateProcessTree(rootPid, rootInstance, force, [...observed.values()], true);
|
|
21
|
+
},
|
|
22
|
+
forceQuiescence() {
|
|
23
|
+
return forceProcessTreeQuiescence(rootPid, rootInstance, observed);
|
|
24
|
+
},
|
|
25
|
+
assertQuiescent() {
|
|
26
|
+
return assertProcessTreeQuiescent(rootPid, rootInstance, observed);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
async function observe() {
|
|
30
|
+
do {
|
|
31
|
+
const snapshot = await processSnapshot();
|
|
32
|
+
const currentRoot = snapshot.find((entry) => entry.pid === rootPid);
|
|
33
|
+
if (currentRoot &&
|
|
34
|
+
(!rootInstance || processInstanceMatches(rootInstance, currentRoot))) {
|
|
35
|
+
rootInstance ??= currentRoot;
|
|
36
|
+
for (const descendant of descendantsOf(rootPid, snapshot))
|
|
37
|
+
observed.set(processInstanceKey(descendant), descendant);
|
|
38
|
+
}
|
|
39
|
+
if (stopped)
|
|
40
|
+
return;
|
|
41
|
+
await delay(process.platform === "win32" ? 100 : 25);
|
|
42
|
+
} while (!stopped);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function forceProcessTreeQuiescence(rootPid, rootInstance, observed) {
|
|
46
|
+
await terminateProcessTree(rootPid, rootInstance, true, [...observed.values()], true);
|
|
47
|
+
const deadline = Date.now() + PROCESS_TREE_GRACE_MS;
|
|
48
|
+
while (Date.now() < deadline) {
|
|
49
|
+
const snapshot = await processSnapshot();
|
|
50
|
+
const rootAlive = rootProcessAlive(rootPid, rootInstance, snapshot);
|
|
51
|
+
const descendantsAlive = liveProcessInstances([...observed.values()], snapshot).length;
|
|
52
|
+
const groupAlive = process.platform !== "win32" && processGroupExists(rootPid);
|
|
53
|
+
if (!rootAlive && descendantsAlive === 0 && !groupAlive)
|
|
54
|
+
return;
|
|
55
|
+
await delay(process.platform === "win32" ? 100 : 25);
|
|
56
|
+
}
|
|
57
|
+
throw new Error("process_observer_descendant_process_alive");
|
|
58
|
+
}
|
|
59
|
+
async function assertProcessTreeQuiescent(rootPid, rootInstance, observed) {
|
|
60
|
+
const snapshot = await processSnapshot();
|
|
61
|
+
const currentRoot = snapshot.find((entry) => entry.pid === rootPid);
|
|
62
|
+
const finalDescendants = currentRoot &&
|
|
63
|
+
(!rootInstance || processInstanceMatches(rootInstance, currentRoot))
|
|
64
|
+
? descendantsOf(rootPid, snapshot)
|
|
65
|
+
: [];
|
|
66
|
+
const candidates = uniqueProcessInstances([
|
|
67
|
+
...observed.values(),
|
|
68
|
+
...finalDescendants,
|
|
69
|
+
]);
|
|
70
|
+
const descendants = liveProcessInstances(candidates, snapshot);
|
|
71
|
+
const processGroupAlive = process.platform !== "win32" && processGroupExists(rootPid);
|
|
72
|
+
if (!descendants.length && !processGroupAlive)
|
|
73
|
+
return;
|
|
74
|
+
await terminateProcessTree(rootPid, rootInstance, true, descendants, false);
|
|
75
|
+
throw new Error("process_observer_descendant_process_alive");
|
|
76
|
+
}
|
|
77
|
+
async function terminateProcessTree(rootPid, rootInstance, force, knownDescendants, includeRoot) {
|
|
78
|
+
if (!Number.isSafeInteger(rootPid) || rootPid <= 0)
|
|
79
|
+
return;
|
|
80
|
+
if (process.platform === "win32") {
|
|
81
|
+
const snapshot = await processSnapshot();
|
|
82
|
+
const currentRoot = snapshot.find((entry) => entry.pid === rootPid);
|
|
83
|
+
const rootTarget = includeRoot &&
|
|
84
|
+
currentRoot &&
|
|
85
|
+
(!rootInstance || processInstanceMatches(rootInstance, currentRoot))
|
|
86
|
+
? currentRoot
|
|
87
|
+
: null;
|
|
88
|
+
const currentDescendants = rootTarget
|
|
89
|
+
? descendantsOf(rootPid, snapshot)
|
|
90
|
+
: [];
|
|
91
|
+
const descendants = liveProcessInstances(uniqueProcessInstances([...knownDescendants, ...currentDescendants]), snapshot).reverse();
|
|
92
|
+
const targets = rootTarget ? [...descendants, rootTarget] : descendants;
|
|
93
|
+
for (const target of targets)
|
|
94
|
+
await execFileAsync("taskkill.exe", ["/PID", String(target.pid), "/T", "/F"], {
|
|
95
|
+
windowsHide: true,
|
|
96
|
+
timeout: PROCESS_TREE_GRACE_MS,
|
|
97
|
+
}).catch(() => undefined);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const signal = force ? "SIGKILL" : "SIGTERM";
|
|
101
|
+
if (includeRoot)
|
|
102
|
+
try {
|
|
103
|
+
process.kill(-rootPid, signal);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (nodeErrorCode(error) !== "ESRCH")
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
const snapshot = await processSnapshot();
|
|
110
|
+
const descendants = liveProcessInstances(knownDescendants, snapshot);
|
|
111
|
+
for (const descendant of descendants.reverse())
|
|
112
|
+
try {
|
|
113
|
+
process.kill(descendant.pid, signal);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (nodeErrorCode(error) !== "ESRCH")
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function processGroupExists(rootPid) {
|
|
121
|
+
try {
|
|
122
|
+
process.kill(-rootPid, 0);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (nodeErrorCode(error) === "ESRCH")
|
|
127
|
+
return false;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function nodeErrorCode(error) {
|
|
132
|
+
return error && typeof error === "object" && "code" in error
|
|
133
|
+
? String(error.code)
|
|
134
|
+
: null;
|
|
135
|
+
}
|
|
136
|
+
function delay(milliseconds) {
|
|
137
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
138
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { assertProtectedRepositoryFile } from "./repository-path-safety.js";
|
|
1
|
+
export { assertProtectedRepositoryDirectory, assertProtectedRepositoryFile, } from "./repository-path-safety.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { assertProtectedRepositoryFile } from "./repository-path-safety.js";
|
|
1
|
+
export { assertProtectedRepositoryDirectory, assertProtectedRepositoryFile, } from "./repository-path-safety.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CompiledClaimsV2 } from "./long-task-claims.js";
|
|
2
2
|
import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
3
3
|
import { type Reporter } from "./long-task-ui-surface-validation.js";
|
|
4
|
-
|
|
4
|
+
import type { DeliveryContractStructureValidationOptions } from "./long-task-delivery-validation.js";
|
|
5
|
+
export declare function validateUiSurfaceBindings(contract: DeliveryContractV2, claims: CompiledClaimsV2, report?: Reporter, options?: DeliveryContractStructureValidationOptions): void;
|