///|
fn parse_operation_kind(kind : String) -> PlanOperationKind raise {
match kind {
"RemovePart" => RemovePart
"RemoveRelationship" => RemoveRelationship
"RewriteSourceXml" => RewriteSourceXml
"RewriteRelationshipPart" => RewriteRelationshipPart
"RewriteContentTypes" => RewriteContentTypes
"ChangeMainContentType" => ChangeMainContentType
_ => raise Refused("unknown planner operation")
}
}
///|
fn operation_writes(kind : PlanOperationKind) -> Bool {
match kind {
RemovePart
| RewriteSourceXml
| RewriteRelationshipPart
| RewriteContentTypes => true
RemoveRelationship | ChangeMainContentType => false
}
}
///|
fn validate_operation_conflicts(
operations : Array[CheckedOperation],
) -> Unit raise {
let ids : Map[String, Bool] = Map([])
let keys : Map[String, Bool] = Map([])
let writers : Map[String, String] = Map([])
for operation in operations {
guard !ids.contains(operation.operation_id) else {
raise Invalid("duplicate operation identity")
}
let key = operation.kind.label() +
"|" +
operation.part +
"|" +
operation.relationship_id
guard !keys.contains(key) else { raise Invalid("duplicate plan operation") }
for dependency in operation.depends_on {
guard ids.contains(dependency) else {
raise Invalid("operation dependency missing, cyclic or out of order")
}
}
if operation_writes(operation.kind) {
guard !writers.contains(operation.part) else {
raise Invalid("conflicting payload operations: " + operation.part)
}
writers[operation.part] = operation.operation_id
}
ids[operation.operation_id] = true
keys[key] = true
}
}
///|
fn validate_removal_closure(
pkg : Package,
prepared : PreparedPlan,
) -> Unit raise {
let removed : Map[String, Bool] = Map([])
for part in prepared.controlled_removals {
guard part != pkg.audit.main_part && part != "[Content_Types].xml" else {
raise Invalid("protected part selected for deletion")
}
guard !removed.contains(part) && pkg.graph.part_index.contains(part) else {
raise Invalid("duplicate/missing controlled deletion")
}
removed[part] = true
guard prepared.operations.any(fn(op) {
op.kind == RemovePart && op.part == part
}) else {
raise Invalid("controlled deletion missing its operation")
}
}
for r in pkg.audit.relationships {
if !r.external &&
removed.contains(r.resolved) &&
!removed.contains(r.source) {
guard prepared.operations.any(fn(op) {
op.kind == RemoveRelationship &&
op.source == r.source &&
op.relationship_id == r.id
}) else {
raise Refused("retained relationship still requires controlled target")
}
}
}
for reference in pkg.graph.graph.xml_references {
let r = pkg.audit.relationships[reference.relationship_index]
guard !removed.contains(r.resolved) || removed.contains(reference.source) else {
raise Refused(
"retained XML reference cannot be repaired by the VBA handler",
)
}
}
}
///|
fn make_prepared_plan(pkg : Package, limits : Limits) -> PreparedPlan raise {
let assessment = assess_package(pkg)
guard assessment.decision.result == Pass &&
assessment.decision.rebuild_allowed else {
raise Refused(assessment.decision.reason)
}
let decisions : Array[PolicyDecision] = []
let finding_decisions : Map[String, String] = Map([])
for i, finding in assessment.findings {
guard finding.capability == Vba && finding.rebuild_support == Supported else {
raise Refused("finding has no accepted planner handler")
}
let id = "finding-" + i.to_string()
finding_decisions[finding.part] = id
decisions.push({
decision_id: id,
finding_index: Some(i),
action: RemoveAction,
rule_id: finding.rule_id,
part: finding.part,
reason: "Remove VBA and its exclusively owned supporting parts under passive-office-v1.",
})
}
if is_macro_format(pkg.audit.format) {
decisions.push({
decision_id: "format",
finding_index: None,
action: AllowAction,
rule_id: "format-v1",
part: pkg.audit.main_part,
reason: "Convert macro-enabled main type; this does not assert that VBA is present.",
})
}
let operations : Array[CheckedOperation] = []
let removed = controlled_vba_parts(pkg)
for i, operation in make_plan(pkg).operations {
let kind = parse_operation_kind(operation.kind)
let part = if kind == RemoveRelationship {
relationship_part(operation.part)
} else if kind == ChangeMainContentType {
"[Content_Types].xml"
} else {
operation.part
}
let before = pkg.audit.parts[pkg.graph.part_index[part]]
let after = match kind {
RemovePart => None
RewriteRelationshipPart =>
Some(digest(write_relationships(pkg, pkg.audit.main_part)[:]))
RewriteContentTypes => Some(digest(write_content_types(pkg)[:]))
RewriteSourceXml =>
raise Refused("source XML handler has not passed acceptance")
RemoveRelationship | ChangeMainContentType => Some(before.sha256)
}
let depends_on : Array[String] = []
for previous in operations {
if (
kind == RewriteRelationshipPart &&
previous.kind == RemoveRelationship &&
previous.part == part
) ||
(
kind == RewriteContentTypes &&
(
previous.kind == RemovePart ||
previous.kind == ChangeMainContentType
)
) {
depends_on.push(previous.operation_id)
}
}
let decision_id = if operation.rule == "format-v1" {
"format"
} else if finding_decisions.get(operation.part) is Some(id) {
id
} else if pkg.audit.findings.length() == 1 &&
finding_decisions.get(pkg.audit.findings[0].part) is Some(id) {
id
} else {
raise Invalid("operation has no finding/policy decision")
}
operations.push({
operation_id: "op-" + i.to_string(),
kind,
part,
source: operation.part,
relationship_id: operation.relationship_id,
rule_id: operation.rule,
decision_id,
before_hash: before.sha256,
expected_after_hash: after,
depends_on,
reason: operation.reason,
})
}
let prepared : PreparedPlan = {
schema: "partsieve.prepared-plan.v1",
tool: "partsieve/0.2.0-spike",
rules: "bounded-vba-plan-v1",
input_hash: pkg.audit.input_hash,
input_format: pkg.audit.format,
output_format: if is_word_format(pkg.audit.format) {
"DOCX"
} else {
"XLSX"
},
policy: "passive-office-v1",
profile: pkg.audit.profile,
limits,
preconditions: pkg.audit.parts,
findings: assessment.findings,
decisions,
operations,
controlled_removals: removed,
orphan_candidates: pkg.graph.graph.orphan_candidates,
coverage: assessment.coverage,
gate: assessment.decision,
unavailable_handlers: [
"RewriteSourceXml: no source XML rewrite handler has passed acceptance.",
],
}
validate_operation_conflicts(prepared.operations)
validate_removal_closure(pkg, prepared)
prepared
}
///|
pub fn prepare(
input : Bytes,
limits? : Limits = Limits::default(),
) -> PreparedPlan raise {
make_prepared_plan(load_package(input, limits), limits)
}
///|
fn execute_prepared(
pkg : Package,
prepared : PreparedPlan,
limits : Limits,
) -> Bytes raise {
guard prepared.input_hash == pkg.audit.input_hash &&
prepared.limits == limits &&
prepared.preconditions == pkg.audit.parts else {
raise Invalid("plan input/hash/resource precondition mismatch")
}
validate_operation_conflicts(prepared.operations)
validate_removal_closure(pkg, prepared)
let archive = pkg.archive.clone()
for operation in prepared.operations {
let before = pkg.audit.parts[pkg.graph.part_index[operation.part]]
guard before.sha256 == operation.before_hash else {
raise Invalid("operation payload precondition mismatch")
}
match operation.kind {
RemovePart => {
guard prepared.controlled_removals.contains(operation.part) &&
archive.remove(operation.part[:]) else {
raise Invalid("operation controlled removal precondition mismatch")
}
}
RemoveRelationship => {
guard graph_relationship(
pkg.graph,
operation.source,
operation.relationship_id,
)
is Some(r) &&
r.kind == vba_rel else {
raise Invalid("relationship operation precondition mismatch")
}
}
ChangeMainContentType => {
guard is_macro_format(pkg.audit.format) &&
operation.source == pkg.audit.main_part else {
raise Invalid("format operation precondition mismatch")
}
}
RewriteSourceXml =>
raise Refused("source XML handler has not passed acceptance")
RewriteRelationshipPart | RewriteContentTypes => {
let payload = if operation.kind == RewriteContentTypes {
write_content_types(pkg)
} else {
write_relationships(pkg, pkg.audit.main_part)
}
guard operation.expected_after_hash == Some(digest(payload[:])) &&
archive.replace(operation.part[:], payload[:]) else {
raise Invalid("metadata operation postcondition mismatch")
}
}
}
}
@zip.write_preserving_limited(archive, max_output_bytes=limits.output_bytes) catch {
@zip.ZipError(_, message) =>
raise Incomplete("bounded output writing did not complete: " + message)
}
}
///|
// A supplied JSON plan is data, not authority. Recompute every field from the
// actual bounded input before execution, then verify the serialized output.
fn execute_supplied_plan(
input : Bytes,
supplied : Json,
limits : Limits,
) -> Bytes raise {
let pkg = load_package(input, limits)
let expected = make_prepared_plan(pkg, limits)
guard supplied == expected.to_json() else {
raise Invalid("supplied plan differs from independently recomputed plan")
}
execute_prepared(pkg, expected, limits)
}
///|
pub fn rebuild_with_plan(
input : Bytes,
supplied : Json,
limits? : Limits = Limits::default(),
) -> Rebuilt raise {
let bytes = execute_supplied_plan(input, supplied, limits)
{ bytes, receipt: verify(bytes, input, limits~), }
}