@tiangong-lca/pcr 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/NOTICE.md +5 -0
- package/README.md +86 -2
- package/node_modules/ajv/.runkit_example.js +23 -0
- package/node_modules/ajv/LICENSE +22 -0
- package/node_modules/ajv/README.md +207 -0
- package/node_modules/ajv/dist/2019.d.ts +19 -0
- package/node_modules/ajv/dist/2019.js +61 -0
- package/node_modules/ajv/dist/2019.js.map +1 -0
- package/node_modules/ajv/dist/2020.d.ts +19 -0
- package/node_modules/ajv/dist/2020.js +55 -0
- package/node_modules/ajv/dist/2020.js.map +1 -0
- package/node_modules/ajv/dist/ajv.d.ts +18 -0
- package/node_modules/ajv/dist/ajv.js +50 -0
- package/node_modules/ajv/dist/ajv.js.map +1 -0
- package/node_modules/ajv/dist/compile/codegen/code.d.ts +40 -0
- package/node_modules/ajv/dist/compile/codegen/code.js +156 -0
- package/node_modules/ajv/dist/compile/codegen/code.js.map +1 -0
- package/node_modules/ajv/dist/compile/codegen/index.d.ts +79 -0
- package/node_modules/ajv/dist/compile/codegen/index.js +697 -0
- package/node_modules/ajv/dist/compile/codegen/index.js.map +1 -0
- package/node_modules/ajv/dist/compile/codegen/scope.d.ts +79 -0
- package/node_modules/ajv/dist/compile/codegen/scope.js +143 -0
- package/node_modules/ajv/dist/compile/codegen/scope.js.map +1 -0
- package/node_modules/ajv/dist/compile/errors.d.ts +13 -0
- package/node_modules/ajv/dist/compile/errors.js +123 -0
- package/node_modules/ajv/dist/compile/errors.js.map +1 -0
- package/node_modules/ajv/dist/compile/index.d.ts +80 -0
- package/node_modules/ajv/dist/compile/index.js +242 -0
- package/node_modules/ajv/dist/compile/index.js.map +1 -0
- package/node_modules/ajv/dist/compile/jtd/parse.d.ts +4 -0
- package/node_modules/ajv/dist/compile/jtd/parse.js +350 -0
- package/node_modules/ajv/dist/compile/jtd/parse.js.map +1 -0
- package/node_modules/ajv/dist/compile/jtd/serialize.d.ts +4 -0
- package/node_modules/ajv/dist/compile/jtd/serialize.js +236 -0
- package/node_modules/ajv/dist/compile/jtd/serialize.js.map +1 -0
- package/node_modules/ajv/dist/compile/jtd/types.d.ts +6 -0
- package/node_modules/ajv/dist/compile/jtd/types.js +14 -0
- package/node_modules/ajv/dist/compile/jtd/types.js.map +1 -0
- package/node_modules/ajv/dist/compile/names.d.ts +20 -0
- package/node_modules/ajv/dist/compile/names.js +28 -0
- package/node_modules/ajv/dist/compile/names.js.map +1 -0
- package/node_modules/ajv/dist/compile/ref_error.d.ts +6 -0
- package/node_modules/ajv/dist/compile/ref_error.js +12 -0
- package/node_modules/ajv/dist/compile/ref_error.js.map +1 -0
- package/node_modules/ajv/dist/compile/resolve.d.ts +12 -0
- package/node_modules/ajv/dist/compile/resolve.js +155 -0
- package/node_modules/ajv/dist/compile/resolve.js.map +1 -0
- package/node_modules/ajv/dist/compile/rules.d.ts +28 -0
- package/node_modules/ajv/dist/compile/rules.js +26 -0
- package/node_modules/ajv/dist/compile/rules.js.map +1 -0
- package/node_modules/ajv/dist/compile/util.d.ts +40 -0
- package/node_modules/ajv/dist/compile/util.js +178 -0
- package/node_modules/ajv/dist/compile/util.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/applicability.d.ts +6 -0
- package/node_modules/ajv/dist/compile/validate/applicability.js +19 -0
- package/node_modules/ajv/dist/compile/validate/applicability.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/boolSchema.d.ts +4 -0
- package/node_modules/ajv/dist/compile/validate/boolSchema.js +50 -0
- package/node_modules/ajv/dist/compile/validate/boolSchema.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/dataType.d.ts +17 -0
- package/node_modules/ajv/dist/compile/validate/dataType.js +203 -0
- package/node_modules/ajv/dist/compile/validate/dataType.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/defaults.d.ts +2 -0
- package/node_modules/ajv/dist/compile/validate/defaults.js +35 -0
- package/node_modules/ajv/dist/compile/validate/defaults.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/index.d.ts +42 -0
- package/node_modules/ajv/dist/compile/validate/index.js +520 -0
- package/node_modules/ajv/dist/compile/validate/index.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/keyword.d.ts +8 -0
- package/node_modules/ajv/dist/compile/validate/keyword.js +124 -0
- package/node_modules/ajv/dist/compile/validate/keyword.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/subschema.d.ts +47 -0
- package/node_modules/ajv/dist/compile/validate/subschema.js +81 -0
- package/node_modules/ajv/dist/compile/validate/subschema.js.map +1 -0
- package/node_modules/ajv/dist/core.d.ts +174 -0
- package/node_modules/ajv/dist/core.js +618 -0
- package/node_modules/ajv/dist/core.js.map +1 -0
- package/node_modules/ajv/dist/jtd.d.ts +47 -0
- package/node_modules/ajv/dist/jtd.js +72 -0
- package/node_modules/ajv/dist/jtd.js.map +1 -0
- package/node_modules/ajv/dist/refs/data.json +13 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/index.d.ts +2 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/index.js +28 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/index.js.map +1 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/applicator.json +53 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/content.json +17 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/core.json +57 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/format.json +14 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/meta-data.json +37 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json +90 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/schema.json +39 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/index.d.ts +2 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/index.js +30 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/index.js.map +1 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/applicator.json +48 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/content.json +17 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/core.json +51 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/format-annotation.json +14 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/meta-data.json +37 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/unevaluated.json +15 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json +90 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/schema.json +55 -0
- package/node_modules/ajv/dist/refs/json-schema-draft-06.json +137 -0
- package/node_modules/ajv/dist/refs/json-schema-draft-07.json +151 -0
- package/node_modules/ajv/dist/refs/json-schema-secure.json +88 -0
- package/node_modules/ajv/dist/refs/jtd-schema.d.ts +3 -0
- package/node_modules/ajv/dist/refs/jtd-schema.js +118 -0
- package/node_modules/ajv/dist/refs/jtd-schema.js.map +1 -0
- package/node_modules/ajv/dist/runtime/equal.d.ts +6 -0
- package/node_modules/ajv/dist/runtime/equal.js +7 -0
- package/node_modules/ajv/dist/runtime/equal.js.map +1 -0
- package/node_modules/ajv/dist/runtime/parseJson.d.ts +18 -0
- package/node_modules/ajv/dist/runtime/parseJson.js +185 -0
- package/node_modules/ajv/dist/runtime/parseJson.js.map +1 -0
- package/node_modules/ajv/dist/runtime/quote.d.ts +5 -0
- package/node_modules/ajv/dist/runtime/quote.js +30 -0
- package/node_modules/ajv/dist/runtime/quote.js.map +1 -0
- package/node_modules/ajv/dist/runtime/re2.d.ts +6 -0
- package/node_modules/ajv/dist/runtime/re2.js +6 -0
- package/node_modules/ajv/dist/runtime/re2.js.map +1 -0
- package/node_modules/ajv/dist/runtime/timestamp.d.ts +5 -0
- package/node_modules/ajv/dist/runtime/timestamp.js +42 -0
- package/node_modules/ajv/dist/runtime/timestamp.js.map +1 -0
- package/node_modules/ajv/dist/runtime/ucs2length.d.ts +5 -0
- package/node_modules/ajv/dist/runtime/ucs2length.js +24 -0
- package/node_modules/ajv/dist/runtime/ucs2length.js.map +1 -0
- package/node_modules/ajv/dist/runtime/uri.d.ts +6 -0
- package/node_modules/ajv/dist/runtime/uri.js +6 -0
- package/node_modules/ajv/dist/runtime/uri.js.map +1 -0
- package/node_modules/ajv/dist/runtime/validation_error.d.ts +7 -0
- package/node_modules/ajv/dist/runtime/validation_error.js +11 -0
- package/node_modules/ajv/dist/runtime/validation_error.js.map +1 -0
- package/node_modules/ajv/dist/standalone/index.d.ts +6 -0
- package/node_modules/ajv/dist/standalone/index.js +90 -0
- package/node_modules/ajv/dist/standalone/index.js.map +1 -0
- package/node_modules/ajv/dist/standalone/instance.d.ts +12 -0
- package/node_modules/ajv/dist/standalone/instance.js +35 -0
- package/node_modules/ajv/dist/standalone/instance.js.map +1 -0
- package/node_modules/ajv/dist/types/index.d.ts +183 -0
- package/node_modules/ajv/dist/types/index.js +3 -0
- package/node_modules/ajv/dist/types/index.js.map +1 -0
- package/node_modules/ajv/dist/types/json-schema.d.ts +125 -0
- package/node_modules/ajv/dist/types/json-schema.js +3 -0
- package/node_modules/ajv/dist/types/json-schema.js.map +1 -0
- package/node_modules/ajv/dist/types/jtd-schema.d.ts +174 -0
- package/node_modules/ajv/dist/types/jtd-schema.js +3 -0
- package/node_modules/ajv/dist/types/jtd-schema.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalItems.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalItems.js +49 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalItems.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js +106 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/allOf.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/allOf.js +23 -0
- package/node_modules/ajv/dist/vocabularies/applicator/allOf.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/anyOf.d.ts +4 -0
- package/node_modules/ajv/dist/vocabularies/applicator/anyOf.js +12 -0
- package/node_modules/ajv/dist/vocabularies/applicator/anyOf.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/contains.d.ts +7 -0
- package/node_modules/ajv/dist/vocabularies/applicator/contains.js +95 -0
- package/node_modules/ajv/dist/vocabularies/applicator/contains.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependencies.d.ts +21 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependencies.js +85 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependencies.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependentSchemas.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependentSchemas.js +11 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependentSchemas.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/if.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/if.js +66 -0
- package/node_modules/ajv/dist/vocabularies/applicator/if.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/index.d.ts +13 -0
- package/node_modules/ajv/dist/vocabularies/applicator/index.js +44 -0
- package/node_modules/ajv/dist/vocabularies/applicator/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items.js +52 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items2020.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items2020.js +30 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items2020.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/not.d.ts +4 -0
- package/node_modules/ajv/dist/vocabularies/applicator/not.js +26 -0
- package/node_modules/ajv/dist/vocabularies/applicator/not.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/oneOf.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/oneOf.js +60 -0
- package/node_modules/ajv/dist/vocabularies/applicator/oneOf.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/patternProperties.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/patternProperties.js +75 -0
- package/node_modules/ajv/dist/vocabularies/applicator/patternProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/prefixItems.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/prefixItems.js +12 -0
- package/node_modules/ajv/dist/vocabularies/applicator/prefixItems.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/properties.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/properties.js +54 -0
- package/node_modules/ajv/dist/vocabularies/applicator/properties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/propertyNames.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/propertyNames.js +38 -0
- package/node_modules/ajv/dist/vocabularies/applicator/propertyNames.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/thenElse.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/thenElse.js +13 -0
- package/node_modules/ajv/dist/vocabularies/applicator/thenElse.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/code.d.ts +17 -0
- package/node_modules/ajv/dist/vocabularies/code.js +131 -0
- package/node_modules/ajv/dist/vocabularies/code.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/core/id.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/core/id.js +10 -0
- package/node_modules/ajv/dist/vocabularies/core/id.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/core/index.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/core/index.js +16 -0
- package/node_modules/ajv/dist/vocabularies/core/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/core/ref.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/core/ref.js +122 -0
- package/node_modules/ajv/dist/vocabularies/core/ref.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/index.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/index.js +104 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/types.d.ts +10 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/types.js +9 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/types.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/draft2020.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/draft2020.js +23 -0
- package/node_modules/ajv/dist/vocabularies/draft2020.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/draft7.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/draft7.js +17 -0
- package/node_modules/ajv/dist/vocabularies/draft7.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicAnchor.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicAnchor.js +30 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicAnchor.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicRef.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicRef.js +51 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicRef.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/index.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/index.js +9 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveAnchor.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveAnchor.js +16 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveAnchor.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveRef.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveRef.js +10 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveRef.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/errors.d.ts +9 -0
- package/node_modules/ajv/dist/vocabularies/errors.js +3 -0
- package/node_modules/ajv/dist/vocabularies/errors.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/format/format.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/format/format.js +92 -0
- package/node_modules/ajv/dist/vocabularies/format/format.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/format/index.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/format/index.js +6 -0
- package/node_modules/ajv/dist/vocabularies/format/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/discriminator.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/jtd/discriminator.js +71 -0
- package/node_modules/ajv/dist/vocabularies/jtd/discriminator.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/elements.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/jtd/elements.js +24 -0
- package/node_modules/ajv/dist/vocabularies/jtd/elements.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/enum.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/jtd/enum.js +43 -0
- package/node_modules/ajv/dist/vocabularies/jtd/enum.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/error.d.ts +9 -0
- package/node_modules/ajv/dist/vocabularies/jtd/error.js +20 -0
- package/node_modules/ajv/dist/vocabularies/jtd/error.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/index.d.ts +10 -0
- package/node_modules/ajv/dist/vocabularies/jtd/index.js +29 -0
- package/node_modules/ajv/dist/vocabularies/jtd/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/metadata.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/jtd/metadata.js +25 -0
- package/node_modules/ajv/dist/vocabularies/jtd/metadata.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/nullable.d.ts +4 -0
- package/node_modules/ajv/dist/vocabularies/jtd/nullable.js +22 -0
- package/node_modules/ajv/dist/vocabularies/jtd/nullable.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/optionalProperties.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/jtd/optionalProperties.js +15 -0
- package/node_modules/ajv/dist/vocabularies/jtd/optionalProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/properties.d.ts +22 -0
- package/node_modules/ajv/dist/vocabularies/jtd/properties.js +149 -0
- package/node_modules/ajv/dist/vocabularies/jtd/properties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/ref.d.ts +4 -0
- package/node_modules/ajv/dist/vocabularies/jtd/ref.js +67 -0
- package/node_modules/ajv/dist/vocabularies/jtd/ref.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/type.d.ts +10 -0
- package/node_modules/ajv/dist/vocabularies/jtd/type.js +69 -0
- package/node_modules/ajv/dist/vocabularies/jtd/type.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/union.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/jtd/union.js +12 -0
- package/node_modules/ajv/dist/vocabularies/jtd/union.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/values.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/jtd/values.js +51 -0
- package/node_modules/ajv/dist/vocabularies/jtd/values.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/metadata.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/metadata.js +18 -0
- package/node_modules/ajv/dist/vocabularies/metadata.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/next.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/next.js +8 -0
- package/node_modules/ajv/dist/vocabularies/next.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/index.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/index.js +7 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedItems.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedItems.js +40 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedItems.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedProperties.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedProperties.js +65 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/const.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/validation/const.js +25 -0
- package/node_modules/ajv/dist/vocabularies/validation/const.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/dependentRequired.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/validation/dependentRequired.js +12 -0
- package/node_modules/ajv/dist/vocabularies/validation/dependentRequired.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/enum.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/validation/enum.js +48 -0
- package/node_modules/ajv/dist/vocabularies/validation/enum.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/index.d.ts +16 -0
- package/node_modules/ajv/dist/vocabularies/validation/index.js +33 -0
- package/node_modules/ajv/dist/vocabularies/validation/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitContains.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitContains.js +15 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitContains.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitItems.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitItems.js +24 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitItems.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitLength.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitLength.js +27 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitLength.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitNumber.d.ts +11 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitNumber.js +27 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitNumber.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitProperties.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitProperties.js +24 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/multipleOf.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/validation/multipleOf.js +26 -0
- package/node_modules/ajv/dist/vocabularies/validation/multipleOf.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/pattern.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/validation/pattern.js +33 -0
- package/node_modules/ajv/dist/vocabularies/validation/pattern.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/required.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/validation/required.js +79 -0
- package/node_modules/ajv/dist/vocabularies/validation/required.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/uniqueItems.d.ts +9 -0
- package/node_modules/ajv/dist/vocabularies/validation/uniqueItems.js +64 -0
- package/node_modules/ajv/dist/vocabularies/validation/uniqueItems.js.map +1 -0
- package/node_modules/ajv/lib/2019.ts +81 -0
- package/node_modules/ajv/lib/2020.ts +75 -0
- package/node_modules/ajv/lib/ajv.ts +70 -0
- package/node_modules/ajv/lib/compile/codegen/code.ts +169 -0
- package/node_modules/ajv/lib/compile/codegen/index.ts +852 -0
- package/node_modules/ajv/lib/compile/codegen/scope.ts +215 -0
- package/node_modules/ajv/lib/compile/errors.ts +184 -0
- package/node_modules/ajv/lib/compile/index.ts +324 -0
- package/node_modules/ajv/lib/compile/jtd/parse.ts +411 -0
- package/node_modules/ajv/lib/compile/jtd/serialize.ts +277 -0
- package/node_modules/ajv/lib/compile/jtd/types.ts +16 -0
- package/node_modules/ajv/lib/compile/names.ts +27 -0
- package/node_modules/ajv/lib/compile/ref_error.ts +13 -0
- package/node_modules/ajv/lib/compile/resolve.ts +149 -0
- package/node_modules/ajv/lib/compile/rules.ts +50 -0
- package/node_modules/ajv/lib/compile/util.ts +213 -0
- package/node_modules/ajv/lib/compile/validate/applicability.ts +22 -0
- package/node_modules/ajv/lib/compile/validate/boolSchema.ts +47 -0
- package/node_modules/ajv/lib/compile/validate/dataType.ts +230 -0
- package/node_modules/ajv/lib/compile/validate/defaults.ts +32 -0
- package/node_modules/ajv/lib/compile/validate/index.ts +582 -0
- package/node_modules/ajv/lib/compile/validate/keyword.ts +171 -0
- package/node_modules/ajv/lib/compile/validate/subschema.ts +135 -0
- package/node_modules/ajv/lib/core.ts +892 -0
- package/node_modules/ajv/lib/jtd.ts +132 -0
- package/node_modules/ajv/lib/refs/data.json +13 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/index.ts +28 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/applicator.json +53 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/content.json +17 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/core.json +57 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/format.json +14 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/meta-data.json +37 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/validation.json +90 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/schema.json +39 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/index.ts +30 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/applicator.json +48 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/content.json +17 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/core.json +51 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/format-annotation.json +14 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/meta-data.json +37 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/unevaluated.json +15 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/validation.json +90 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/schema.json +55 -0
- package/node_modules/ajv/lib/refs/json-schema-draft-06.json +137 -0
- package/node_modules/ajv/lib/refs/json-schema-draft-07.json +151 -0
- package/node_modules/ajv/lib/refs/json-schema-secure.json +88 -0
- package/node_modules/ajv/lib/refs/jtd-schema.ts +130 -0
- package/node_modules/ajv/lib/runtime/equal.ts +7 -0
- package/node_modules/ajv/lib/runtime/parseJson.ts +177 -0
- package/node_modules/ajv/lib/runtime/quote.ts +31 -0
- package/node_modules/ajv/lib/runtime/re2.ts +6 -0
- package/node_modules/ajv/lib/runtime/timestamp.ts +46 -0
- package/node_modules/ajv/lib/runtime/ucs2length.ts +20 -0
- package/node_modules/ajv/lib/runtime/uri.ts +6 -0
- package/node_modules/ajv/lib/runtime/validation_error.ts +13 -0
- package/node_modules/ajv/lib/standalone/index.ts +100 -0
- package/node_modules/ajv/lib/standalone/instance.ts +36 -0
- package/node_modules/ajv/lib/types/index.ts +244 -0
- package/node_modules/ajv/lib/types/json-schema.ts +187 -0
- package/node_modules/ajv/lib/types/jtd-schema.ts +273 -0
- package/node_modules/ajv/lib/vocabularies/applicator/additionalItems.ts +56 -0
- package/node_modules/ajv/lib/vocabularies/applicator/additionalProperties.ts +118 -0
- package/node_modules/ajv/lib/vocabularies/applicator/allOf.ts +22 -0
- package/node_modules/ajv/lib/vocabularies/applicator/anyOf.ts +14 -0
- package/node_modules/ajv/lib/vocabularies/applicator/contains.ts +109 -0
- package/node_modules/ajv/lib/vocabularies/applicator/dependencies.ts +112 -0
- package/node_modules/ajv/lib/vocabularies/applicator/dependentSchemas.ts +11 -0
- package/node_modules/ajv/lib/vocabularies/applicator/if.ts +80 -0
- package/node_modules/ajv/lib/vocabularies/applicator/index.ts +53 -0
- package/node_modules/ajv/lib/vocabularies/applicator/items.ts +59 -0
- package/node_modules/ajv/lib/vocabularies/applicator/items2020.ts +36 -0
- package/node_modules/ajv/lib/vocabularies/applicator/not.ts +38 -0
- package/node_modules/ajv/lib/vocabularies/applicator/oneOf.ts +82 -0
- package/node_modules/ajv/lib/vocabularies/applicator/patternProperties.ts +91 -0
- package/node_modules/ajv/lib/vocabularies/applicator/prefixItems.ts +12 -0
- package/node_modules/ajv/lib/vocabularies/applicator/properties.ts +57 -0
- package/node_modules/ajv/lib/vocabularies/applicator/propertyNames.ts +50 -0
- package/node_modules/ajv/lib/vocabularies/applicator/thenElse.ts +13 -0
- package/node_modules/ajv/lib/vocabularies/code.ts +168 -0
- package/node_modules/ajv/lib/vocabularies/core/id.ts +10 -0
- package/node_modules/ajv/lib/vocabularies/core/index.ts +16 -0
- package/node_modules/ajv/lib/vocabularies/core/ref.ts +129 -0
- package/node_modules/ajv/lib/vocabularies/discriminator/index.ts +113 -0
- package/node_modules/ajv/lib/vocabularies/discriminator/types.ts +12 -0
- package/node_modules/ajv/lib/vocabularies/draft2020.ts +23 -0
- package/node_modules/ajv/lib/vocabularies/draft7.ts +17 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/dynamicAnchor.ts +31 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/dynamicRef.ts +51 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/index.ts +9 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/recursiveAnchor.ts +14 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/recursiveRef.ts +10 -0
- package/node_modules/ajv/lib/vocabularies/errors.ts +18 -0
- package/node_modules/ajv/lib/vocabularies/format/format.ts +120 -0
- package/node_modules/ajv/lib/vocabularies/format/index.ts +6 -0
- package/node_modules/ajv/lib/vocabularies/jtd/discriminator.ts +89 -0
- package/node_modules/ajv/lib/vocabularies/jtd/elements.ts +32 -0
- package/node_modules/ajv/lib/vocabularies/jtd/enum.ts +45 -0
- package/node_modules/ajv/lib/vocabularies/jtd/error.ts +23 -0
- package/node_modules/ajv/lib/vocabularies/jtd/index.ts +37 -0
- package/node_modules/ajv/lib/vocabularies/jtd/metadata.ts +24 -0
- package/node_modules/ajv/lib/vocabularies/jtd/nullable.ts +21 -0
- package/node_modules/ajv/lib/vocabularies/jtd/optionalProperties.ts +15 -0
- package/node_modules/ajv/lib/vocabularies/jtd/properties.ts +184 -0
- package/node_modules/ajv/lib/vocabularies/jtd/ref.ts +76 -0
- package/node_modules/ajv/lib/vocabularies/jtd/type.ts +75 -0
- package/node_modules/ajv/lib/vocabularies/jtd/union.ts +12 -0
- package/node_modules/ajv/lib/vocabularies/jtd/values.ts +58 -0
- package/node_modules/ajv/lib/vocabularies/metadata.ts +17 -0
- package/node_modules/ajv/lib/vocabularies/next.ts +8 -0
- package/node_modules/ajv/lib/vocabularies/unevaluated/index.ts +7 -0
- package/node_modules/ajv/lib/vocabularies/unevaluated/unevaluatedItems.ts +47 -0
- package/node_modules/ajv/lib/vocabularies/unevaluated/unevaluatedProperties.ts +85 -0
- package/node_modules/ajv/lib/vocabularies/validation/const.ts +28 -0
- package/node_modules/ajv/lib/vocabularies/validation/dependentRequired.ts +23 -0
- package/node_modules/ajv/lib/vocabularies/validation/enum.ts +54 -0
- package/node_modules/ajv/lib/vocabularies/validation/index.ts +49 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitContains.ts +16 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitItems.ts +26 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitLength.ts +30 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitNumber.ts +42 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitProperties.ts +26 -0
- package/node_modules/ajv/lib/vocabularies/validation/multipleOf.ts +34 -0
- package/node_modules/ajv/lib/vocabularies/validation/pattern.ts +39 -0
- package/node_modules/ajv/lib/vocabularies/validation/required.ts +98 -0
- package/node_modules/ajv/lib/vocabularies/validation/uniqueItems.ts +79 -0
- package/node_modules/ajv/package.json +126 -0
- package/node_modules/fast-deep-equal/LICENSE +21 -0
- package/node_modules/fast-deep-equal/README.md +96 -0
- package/node_modules/fast-deep-equal/es6/index.d.ts +2 -0
- package/node_modules/fast-deep-equal/es6/index.js +72 -0
- package/node_modules/fast-deep-equal/es6/react.d.ts +2 -0
- package/node_modules/fast-deep-equal/es6/react.js +79 -0
- package/node_modules/fast-deep-equal/index.d.ts +4 -0
- package/node_modules/fast-deep-equal/index.js +46 -0
- package/node_modules/fast-deep-equal/package.json +61 -0
- package/node_modules/fast-deep-equal/react.d.ts +2 -0
- package/node_modules/fast-deep-equal/react.js +53 -0
- package/node_modules/fast-uri/.gitattributes +2 -0
- package/node_modules/fast-uri/.github/dependabot.yml +13 -0
- package/node_modules/fast-uri/.github/workflows/ci.yml +106 -0
- package/node_modules/fast-uri/.github/workflows/lock-threads.yml +19 -0
- package/node_modules/fast-uri/.github/workflows/package-manager-ci.yml +24 -0
- package/node_modules/fast-uri/LICENSE +30 -0
- package/node_modules/fast-uri/README.md +152 -0
- package/node_modules/fast-uri/benchmark/benchmark.mjs +159 -0
- package/node_modules/fast-uri/benchmark/equal.mjs +51 -0
- package/node_modules/fast-uri/benchmark/non-simple-domain.mjs +22 -0
- package/node_modules/fast-uri/benchmark/package.json +17 -0
- package/node_modules/fast-uri/benchmark/string-array-to-hex-stripped.mjs +24 -0
- package/node_modules/fast-uri/benchmark/ws-is-secure.mjs +65 -0
- package/node_modules/fast-uri/eslint.config.js +6 -0
- package/node_modules/fast-uri/index.js +627 -0
- package/node_modules/fast-uri/lib/schemes.js +272 -0
- package/node_modules/fast-uri/lib/utils.js +747 -0
- package/node_modules/fast-uri/package.json +68 -0
- package/node_modules/fast-uri/test/ajv.test.js +43 -0
- package/node_modules/fast-uri/test/component-safe-serialization.test.js +163 -0
- package/node_modules/fast-uri/test/equal.test.js +145 -0
- package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +503 -0
- package/node_modules/fast-uri/test/fixtures/uri-js-serialize.json +120 -0
- package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
- package/node_modules/fast-uri/test/ipv6-validation.test.js +124 -0
- package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
- package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
- package/node_modules/fast-uri/test/parse.test.js +327 -0
- package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
- package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
- package/node_modules/fast-uri/test/resolve.test.js +87 -0
- package/node_modules/fast-uri/test/rfc-3986.test.js +90 -0
- package/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
- package/node_modules/fast-uri/test/security-normalization.test.js +140 -0
- package/node_modules/fast-uri/test/security.test.js +431 -0
- package/node_modules/fast-uri/test/serialize.test.js +151 -0
- package/node_modules/fast-uri/test/uri-js-compatibility.test.js +33 -0
- package/node_modules/fast-uri/test/uri-js.test.js +912 -0
- package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
- package/node_modules/fast-uri/test/util.test.js +38 -0
- package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
- package/node_modules/fast-uri/tsconfig.json +9 -0
- package/node_modules/fast-uri/types/index.d.ts +60 -0
- package/node_modules/fast-uri/types/index.test-d.ts +17 -0
- package/node_modules/json-schema-traverse/.eslintrc.yml +27 -0
- package/node_modules/json-schema-traverse/.github/FUNDING.yml +2 -0
- package/node_modules/json-schema-traverse/.github/workflows/build.yml +28 -0
- package/node_modules/json-schema-traverse/.github/workflows/publish.yml +27 -0
- package/node_modules/json-schema-traverse/LICENSE +21 -0
- package/node_modules/json-schema-traverse/README.md +95 -0
- package/node_modules/json-schema-traverse/index.d.ts +40 -0
- package/node_modules/json-schema-traverse/index.js +93 -0
- package/node_modules/json-schema-traverse/package.json +43 -0
- package/node_modules/json-schema-traverse/spec/.eslintrc.yml +6 -0
- package/node_modules/json-schema-traverse/spec/fixtures/schema.js +125 -0
- package/node_modules/json-schema-traverse/spec/index.spec.js +171 -0
- package/node_modules/require-from-string/index.js +34 -0
- package/node_modules/require-from-string/license +21 -0
- package/node_modules/require-from-string/package.json +28 -0
- package/node_modules/require-from-string/readme.md +56 -0
- package/package.json +33 -4
- package/packages/pcr-core/schemas/classification-coverage.schema.json +187 -0
- package/packages/pcr-core/schemas/controlled-vocabulary.schema.json +189 -0
- package/packages/pcr-core/schemas/dataset-validation-input.schema.json +7 -0
- package/packages/pcr-core/schemas/feedback-draft-output.schema.json +12 -0
- package/packages/pcr-core/schemas/feedback.schema.json +27 -0
- package/packages/pcr-core/schemas/guidance-output.schema.json +97 -0
- package/packages/pcr-core/schemas/model-validation-input.schema.json +10 -0
- package/packages/pcr-core/schemas/pcr-id-aliases.schema.json +120 -0
- package/packages/pcr-core/schemas/readiness.schema.json +100 -0
- package/packages/pcr-core/schemas/structured-projection.schema.json +269 -0
- package/packages/pcr-core/schemas/validation-output.schema.json +117 -0
- package/packages/pcr-core/src/classification-coverage.mjs +732 -0
- package/packages/pcr-core/src/contracts.mjs +193 -0
- package/packages/pcr-core/src/generated/controlled-vocabulary.mjs +41 -0
- package/packages/pcr-core/src/index.mjs +2101 -0
- package/packages/pcr-core/src/languages.mjs +86 -0
- package/packages/pcr-core/src/offline-library.mjs +121 -0
- package/packages/pcr-core/src/output-semantics.mjs +228 -0
- package/packages/pcr-core/src/pcr-id-aliases.mjs +949 -0
- package/packages/pcr-core/src/projection-completeness.mjs +284 -0
- package/packages/pcr-core/src/projection-integrity.mjs +208 -0
- package/packages/pcr-core/src/read-context.mjs +505 -0
- package/packages/pcr-core/src/schema-validation.mjs +142 -0
- package/packages/pcr-core/src/source-context.mjs +13 -0
- package/packages/pcr-core/src/yaml-lite.mjs +261 -0
- package/packages/tiangong-pcr-cli/bin/tiangong-pcr.mjs +12 -0
- package/packages/tiangong-pcr-cli/src/commands.mjs +1486 -0
- package/skills/tiangong-pcr/SKILL.md +117 -0
- package/skills/tiangong-pcr/references/cli-usage.md +19 -0
- package/skills/tiangong-pcr/references/feedback-loop.md +16 -0
- package/skills/tiangong-pcr/references/model-authoring-loop.md +13 -0
|
@@ -0,0 +1,2101 @@
|
|
|
1
|
+
import { pcrSource } from "./source-context.mjs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
closeSync,
|
|
5
|
+
constants as fsConstants,
|
|
6
|
+
fstatSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
openSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
realpathSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { TextDecoder } from "node:util";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
PcrClassificationCodeUnknownError,
|
|
18
|
+
PcrClassificationCoverageSemanticError,
|
|
19
|
+
readClassificationCoverage,
|
|
20
|
+
readClassificationCoverageSnapshot,
|
|
21
|
+
} from "./classification-coverage.mjs";
|
|
22
|
+
import { assertCoreContract, validateCoreContract } from "./contracts.mjs";
|
|
23
|
+
import {
|
|
24
|
+
inspectProjectionIntegrity,
|
|
25
|
+
projectionNotRequiredState,
|
|
26
|
+
} from "./projection-integrity.mjs";
|
|
27
|
+
import {
|
|
28
|
+
hasDeclaredUnresolvedReferenceProductFlow,
|
|
29
|
+
materialProjectionCompletenessIssues,
|
|
30
|
+
} from "./projection-completeness.mjs";
|
|
31
|
+
import { findPcrIdAlias as findRepositoryPcrIdAlias } from "./pcr-id-aliases.mjs";
|
|
32
|
+
import {
|
|
33
|
+
assertPcrReadContextFresh,
|
|
34
|
+
createPcrReadContext,
|
|
35
|
+
getPcrReadContextCatalog,
|
|
36
|
+
observePcrReadContextArtifactRead,
|
|
37
|
+
pcrReadContextAliasInputFingerprint,
|
|
38
|
+
withPcrReadContextSession,
|
|
39
|
+
} from "./read-context.mjs";
|
|
40
|
+
import { parseYaml } from "./yaml-lite.mjs";
|
|
41
|
+
import {
|
|
42
|
+
declaredPcrLanguages,
|
|
43
|
+
expectedPcrArtifactHashes,
|
|
44
|
+
LEGACY_PCR_ARTIFACT_HASH_FIELDS,
|
|
45
|
+
pcrArtifactFiles,
|
|
46
|
+
} from "./languages.mjs";
|
|
47
|
+
import {
|
|
48
|
+
CLASSIFICATION_MAPPING_RELATION_VALUES,
|
|
49
|
+
CONTENT_MATURITY_VALUES,
|
|
50
|
+
FEEDBACK_TYPE_VALUES,
|
|
51
|
+
PCR_STATUS_VALUES,
|
|
52
|
+
} from "./generated/controlled-vocabulary.mjs";
|
|
53
|
+
|
|
54
|
+
export const FEEDBACK_TYPES = FEEDBACK_TYPE_VALUES;
|
|
55
|
+
export { createPcrReadContext, pcrReadContextAliasInputFingerprint, withPcrReadContextSession };
|
|
56
|
+
export {
|
|
57
|
+
CLASSIFICATION_COVERAGE_STATUSES,
|
|
58
|
+
PcrClassificationCodeUnknownError,
|
|
59
|
+
PcrClassificationCoverageNotFoundError,
|
|
60
|
+
PcrClassificationCoverageSemanticError,
|
|
61
|
+
PcrClassificationCoverageStatusError,
|
|
62
|
+
classificationCoveragePath,
|
|
63
|
+
findClassificationCoverageEntry,
|
|
64
|
+
getClassificationCoverageSummary,
|
|
65
|
+
hasClassificationCoverage,
|
|
66
|
+
listClassificationCoverage,
|
|
67
|
+
readClassificationCoverage,
|
|
68
|
+
readClassificationCoverageSnapshot,
|
|
69
|
+
} from "./classification-coverage.mjs";
|
|
70
|
+
export const PCR_CATALOG_SCOPES = Object.freeze(["all", "material", "legacy"]);
|
|
71
|
+
export const PCR_RECORD_KINDS = Object.freeze([
|
|
72
|
+
"methodology",
|
|
73
|
+
"legacy_scaffold_reference",
|
|
74
|
+
"invalid_lifecycle_state",
|
|
75
|
+
]);
|
|
76
|
+
const CLASSIFICATION_MAPPING_RELATIONS = new Set(CLASSIFICATION_MAPPING_RELATION_VALUES);
|
|
77
|
+
const PCR_CATALOG_SCOPE_SET = new Set(PCR_CATALOG_SCOPES);
|
|
78
|
+
const METHODOLOGY_LIFECYCLE_STATUSES = new Set(
|
|
79
|
+
PCR_STATUS_VALUES.filter((value) => value !== "scaffold"),
|
|
80
|
+
);
|
|
81
|
+
const METHODOLOGY_MATURITIES = new Set(
|
|
82
|
+
CONTENT_MATURITY_VALUES.filter((value) => value !== "empty_scaffold"),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const CURRENT_SNAPSHOT_MAX_ATTEMPTS = 3;
|
|
86
|
+
const pcrCatalogCache = new Map();
|
|
87
|
+
const RELEASE_ARTIFACTS = LEGACY_PCR_ARTIFACT_HASH_FIELDS;
|
|
88
|
+
const GUIDANCE_MATURITIES = new Set([
|
|
89
|
+
"authored_methodology",
|
|
90
|
+
"reviewed_methodology",
|
|
91
|
+
"published_methodology",
|
|
92
|
+
]);
|
|
93
|
+
const REVIEWED_MATURITIES = new Set(["reviewed_methodology", "published_methodology"]);
|
|
94
|
+
const USABLE_LIFECYCLE_STATES = new Map([
|
|
95
|
+
["candidate", new Set(["authored_methodology"])],
|
|
96
|
+
["active", new Set(["reviewed_methodology"])],
|
|
97
|
+
["published", new Set(["published_methodology"])],
|
|
98
|
+
]);
|
|
99
|
+
const GUIDANCE_LIFECYCLE_STATUSES = new Set(["candidate", "active", "published"]);
|
|
100
|
+
|
|
101
|
+
export class PcrUsabilityError extends Error {
|
|
102
|
+
constructor({ pcrId, operation, readiness }) {
|
|
103
|
+
const blockers = readiness.blockers.map((blocker) => blocker.code).join(", ");
|
|
104
|
+
super(
|
|
105
|
+
`PCR ${pcrId} is not usable for ${operation}: content_maturity=${readiness.methodology_status}; blockers=${blockers || "unknown"}`,
|
|
106
|
+
);
|
|
107
|
+
this.name = "PcrUsabilityError";
|
|
108
|
+
this.code = `PCR_NOT_USABLE_FOR_${operation.toUpperCase()}`;
|
|
109
|
+
this.readiness = structuredClone(readiness);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class PcrClassificationMappingError extends Error {
|
|
114
|
+
constructor({ system, version, code, mappingType, issue = null }) {
|
|
115
|
+
super(issue
|
|
116
|
+
? `Classification mapping ${system}:${version}:${code} is not an accepted current mapping: ${issue}.`
|
|
117
|
+
: `Classification mapping ${system}:${version}:${code} uses unsupported mapping_type ${String(mappingType)}.`);
|
|
118
|
+
this.name = "PcrClassificationMappingError";
|
|
119
|
+
this.code = "PCR_INVALID_CLASSIFICATION_MAPPING";
|
|
120
|
+
this.details = {
|
|
121
|
+
classification: `${system}:${version}:${code}`,
|
|
122
|
+
mapping_type: mappingType ?? null,
|
|
123
|
+
allowed_mapping_types: [...CLASSIFICATION_MAPPING_RELATION_VALUES],
|
|
124
|
+
issue,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class PcrLegacyIdRedirectError extends Error {
|
|
130
|
+
constructor(alias) {
|
|
131
|
+
const redirect = legacyPcrIdRedirect(alias);
|
|
132
|
+
super(
|
|
133
|
+
`PCR id ${redirect.source_pcr_id} is a retired legacy identity. ` +
|
|
134
|
+
`Use the redirect target with: ${redirect.next_command}`,
|
|
135
|
+
);
|
|
136
|
+
this.name = "PcrLegacyIdRedirectError";
|
|
137
|
+
this.code = "PCR_LEGACY_ID_REDIRECT";
|
|
138
|
+
this.details = redirect;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export class PcrClassificationTargetStateError extends Error {
|
|
143
|
+
constructor({ system, version, code, pcr }) {
|
|
144
|
+
super(
|
|
145
|
+
`Classification mapping ${system}:${version}:${code} points to PCR ${pcr.id} with invalid ` +
|
|
146
|
+
`lifecycle identity ${pcr.status}/${pcr.content_maturity}.`,
|
|
147
|
+
);
|
|
148
|
+
this.name = "PcrClassificationTargetStateError";
|
|
149
|
+
this.code = "PCR_INVALID_CLASSIFICATION_TARGET";
|
|
150
|
+
this.details = {
|
|
151
|
+
classification: `${system}:${version}:${code}`,
|
|
152
|
+
pcr_id: pcr.id,
|
|
153
|
+
status: pcr.status,
|
|
154
|
+
content_maturity: pcr.content_maturity,
|
|
155
|
+
record_kind: pcr.record_kind,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class PcrCurrentSnapshotInconsistentError extends Error {
|
|
161
|
+
constructor({ pcrId, pcrPath, attempts, lastFailure }) {
|
|
162
|
+
super(
|
|
163
|
+
`PCR ${pcrId} current snapshot could not be read consistently after ${attempts} attempts.`,
|
|
164
|
+
);
|
|
165
|
+
this.name = "PcrCurrentSnapshotInconsistentError";
|
|
166
|
+
this.code = "PCR_CURRENT_SNAPSHOT_INCONSISTENT";
|
|
167
|
+
this.details = {
|
|
168
|
+
pcr_id: pcrId,
|
|
169
|
+
pcr_path: pcrPath,
|
|
170
|
+
attempts,
|
|
171
|
+
last_failure: lastFailure,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export class PcrDuplicateIdError extends Error {
|
|
177
|
+
constructor({ pcrId, paths }) {
|
|
178
|
+
super(`Duplicate canonical PCR id ${pcrId}: ${paths.join(", ")}`);
|
|
179
|
+
this.name = "PcrDuplicateIdError";
|
|
180
|
+
this.code = "PCR_DUPLICATE_ID";
|
|
181
|
+
this.details = { pcr_id: pcrId, paths: [...paths] };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export class PcrCatalogScopeError extends Error {
|
|
186
|
+
constructor(scope) {
|
|
187
|
+
super(`Unsupported PCR catalog scope ${String(scope)}. Expected one of: ${PCR_CATALOG_SCOPES.join(", ")}.`);
|
|
188
|
+
this.name = "PcrCatalogScopeError";
|
|
189
|
+
this.code = "PCR_INVALID_CATALOG_SCOPE";
|
|
190
|
+
this.details = {
|
|
191
|
+
scope: String(scope),
|
|
192
|
+
allowed_scopes: [...PCR_CATALOG_SCOPES],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function findPcrIdAlias(options) {
|
|
198
|
+
const source = pcrSource(options.root);
|
|
199
|
+
return source ? source.findAlias(options.pcrId) : findRepositoryPcrIdAlias(options);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function listPcrs({ root, refresh = false, scope = "all" }) {
|
|
203
|
+
if (pcrSource(root)) return pcrSource(root).listPcrs(normalizeCatalogScope(scope));
|
|
204
|
+
const normalizedScope = normalizeCatalogScope(scope);
|
|
205
|
+
const normalizedRoot = path.resolve(root);
|
|
206
|
+
const catalog = getPcrCatalog({ root: normalizedRoot, refresh });
|
|
207
|
+
const candidates = normalizedScope === "all"
|
|
208
|
+
? catalog
|
|
209
|
+
: catalog.filter((entry) => currentCatalogEntryMatchesScope(normalizedRoot, entry, normalizedScope));
|
|
210
|
+
return candidates
|
|
211
|
+
.map((entry) => currentPcrEntry(normalizedRoot, entry))
|
|
212
|
+
.filter((entry) => recordMatchesScope(entry, normalizedScope));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function getPcrCatalog({ root, refresh = false }) {
|
|
216
|
+
const normalizedRoot = path.resolve(root);
|
|
217
|
+
if (refresh || !pcrCatalogCache.has(normalizedRoot)) {
|
|
218
|
+
pcrCatalogCache.set(normalizedRoot, readPcrCatalog(normalizedRoot));
|
|
219
|
+
}
|
|
220
|
+
return pcrCatalogCache.get(normalizedRoot);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function readPcrCatalog(root) {
|
|
224
|
+
const pcrRoot = path.join(root, "library/pcrs");
|
|
225
|
+
if (!isCanonicalDirectory(pcrRoot)) {
|
|
226
|
+
throw new Error(`PCR catalog root not found: ${toPosix(path.relative(root, pcrRoot))}`);
|
|
227
|
+
}
|
|
228
|
+
const catalog = findManifestFiles(pcrRoot)
|
|
229
|
+
.map((manifestPath) => {
|
|
230
|
+
const manifest = parseYaml(readCanonicalFileBytes(manifestPath).toString("utf8"));
|
|
231
|
+
const pcrDir = path.dirname(manifestPath);
|
|
232
|
+
return {
|
|
233
|
+
id: manifest.id,
|
|
234
|
+
path: toPosix(path.relative(root, pcrDir)),
|
|
235
|
+
manifestPath,
|
|
236
|
+
};
|
|
237
|
+
})
|
|
238
|
+
.filter((entry) => entry.id);
|
|
239
|
+
const entriesById = new Map();
|
|
240
|
+
for (const entry of catalog) {
|
|
241
|
+
const existing = entriesById.get(entry.id);
|
|
242
|
+
if (existing) {
|
|
243
|
+
throw new PcrDuplicateIdError({
|
|
244
|
+
pcrId: entry.id,
|
|
245
|
+
paths: [existing.path, entry.path].sort(),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
entriesById.set(entry.id, entry);
|
|
249
|
+
}
|
|
250
|
+
return catalog.sort((left, right) => left.id.localeCompare(right.id));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function buildPcrTree({ root, depth = Infinity, scope = "all" }) {
|
|
254
|
+
const tree = {};
|
|
255
|
+
for (const pcr of listPcrs({ root, scope })) {
|
|
256
|
+
const segments = pcr.path.replace(/^library\/pcrs\//u, "").split("/");
|
|
257
|
+
let node = tree;
|
|
258
|
+
for (const [index, segment] of segments.entries()) {
|
|
259
|
+
if (index >= depth) {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
node[segment] ??= { children: {}, pcrs: [] };
|
|
263
|
+
if (index === segments.length - 1) {
|
|
264
|
+
node[segment].pcrs.push(pcr);
|
|
265
|
+
}
|
|
266
|
+
node = node[segment].children;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return tree;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function resolveClassification({ root, system, version, code, context = null }) {
|
|
273
|
+
const normalizedSystem = String(system).toLowerCase();
|
|
274
|
+
const normalizedVersion = String(version);
|
|
275
|
+
const normalizedCode = String(code);
|
|
276
|
+
const coverageIndex = readClassificationCoverage({
|
|
277
|
+
root,
|
|
278
|
+
system: normalizedSystem,
|
|
279
|
+
version: normalizedVersion,
|
|
280
|
+
});
|
|
281
|
+
return resolveClassificationWithCoverage({
|
|
282
|
+
root,
|
|
283
|
+
system: normalizedSystem,
|
|
284
|
+
version: normalizedVersion,
|
|
285
|
+
code: normalizedCode,
|
|
286
|
+
coverageIndex,
|
|
287
|
+
context,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function verifyDistributionCoverage({ root, snapshot, context }) {
|
|
292
|
+
const coverageIndex = snapshot.document;
|
|
293
|
+
for (const entry of coverageIndex.entries) {
|
|
294
|
+
if (entry.coverage_status === "mapped" || entry.legacy_reference) {
|
|
295
|
+
resolveClassificationWithCoverage({ root, system: String(coverageIndex.classification_system).toLowerCase(),
|
|
296
|
+
version: String(coverageIndex.classification_version), code: String(entry.code), coverageIndex, context });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function resolveClassificationWithCoverage({ root, system, version, code, coverageIndex, context = null }) {
|
|
302
|
+
const coverage = coverageIndex.entries.find(
|
|
303
|
+
(candidate) => String(candidate.code) === String(code),
|
|
304
|
+
);
|
|
305
|
+
if (!coverage) {
|
|
306
|
+
throw new PcrClassificationCodeUnknownError({ system, version, code });
|
|
307
|
+
}
|
|
308
|
+
if (coverage.coverage_status === "mapped") {
|
|
309
|
+
const canonical = readCanonicalClassificationMapping({
|
|
310
|
+
root,
|
|
311
|
+
system,
|
|
312
|
+
version,
|
|
313
|
+
code,
|
|
314
|
+
});
|
|
315
|
+
assertCoverageMappingMatchesCanonical({ coverage, canonical, system, version, code });
|
|
316
|
+
const pcr = getPcrById({ root, pcrId: canonical.mapping.pcr_id, context });
|
|
317
|
+
if (pcr.record_kind !== "methodology") {
|
|
318
|
+
throw invalidCoverageResolution({
|
|
319
|
+
system,
|
|
320
|
+
version,
|
|
321
|
+
code,
|
|
322
|
+
issue: `mapped coverage points to non-material PCR ${pcr.id}`,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
classification_system: canonical.mappingFile.classification_system,
|
|
327
|
+
classification_version: canonical.mappingFile.classification_version,
|
|
328
|
+
mapping: canonical.mapping,
|
|
329
|
+
pcr,
|
|
330
|
+
resolution_status: "mapped",
|
|
331
|
+
coverage_status: coverage.coverage_status,
|
|
332
|
+
coverage,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (coverage.legacy_reference) {
|
|
337
|
+
const canonical = readCanonicalClassificationMapping({
|
|
338
|
+
root,
|
|
339
|
+
system,
|
|
340
|
+
version,
|
|
341
|
+
code,
|
|
342
|
+
});
|
|
343
|
+
if (canonical.mapping.pcr_id !== coverage.legacy_reference.pcr_id) {
|
|
344
|
+
throw invalidCoverageResolution({
|
|
345
|
+
system,
|
|
346
|
+
version,
|
|
347
|
+
code,
|
|
348
|
+
issue: `legacy reference ${coverage.legacy_reference.pcr_id} does not match canonical mapping ${canonical.mapping.pcr_id}`,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
const pcr = getPcrById({ root, pcrId: canonical.mapping.pcr_id, context });
|
|
352
|
+
if (pcr.record_kind !== "legacy_scaffold_reference") {
|
|
353
|
+
throw invalidCoverageResolution({
|
|
354
|
+
system,
|
|
355
|
+
version,
|
|
356
|
+
code,
|
|
357
|
+
issue: `legacy reference points to material PCR ${pcr.id}`,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
classification_system: canonical.mappingFile.classification_system,
|
|
362
|
+
classification_version: canonical.mappingFile.classification_version,
|
|
363
|
+
mapping: canonical.mapping,
|
|
364
|
+
pcr,
|
|
365
|
+
resolution_status: "legacy_scaffold_compatibility",
|
|
366
|
+
coverage_status: coverage.coverage_status,
|
|
367
|
+
coverage,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
classification_system: String(coverageIndex.classification_system),
|
|
373
|
+
classification_version: String(coverageIndex.classification_version),
|
|
374
|
+
mapping: null,
|
|
375
|
+
pcr: null,
|
|
376
|
+
resolution_status: "unmapped",
|
|
377
|
+
coverage_status: coverage.coverage_status,
|
|
378
|
+
coverage,
|
|
379
|
+
review_candidate:
|
|
380
|
+
["manual_review", "unknown"].includes(coverage.coverage_status) && coverage.mapping
|
|
381
|
+
? structuredClone(coverage.mapping)
|
|
382
|
+
: null,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function readCanonicalClassificationMapping({ root, system, version, code }) {
|
|
387
|
+
const normalizedRoot = path.resolve(root);
|
|
388
|
+
const mappingPath = path.join(
|
|
389
|
+
normalizedRoot,
|
|
390
|
+
"classifications/mappings",
|
|
391
|
+
`${system}-${version}-to-pcr.yaml`,
|
|
392
|
+
);
|
|
393
|
+
const mappingRelativePath = toPosix(path.relative(normalizedRoot, mappingPath));
|
|
394
|
+
let mappingFile;
|
|
395
|
+
try {
|
|
396
|
+
const mappingBytes = pcrSource(root)?.readFile(mappingRelativePath) ?? readControlledRepositoryFileBytes({
|
|
397
|
+
root: normalizedRoot,
|
|
398
|
+
filePath: mappingPath,
|
|
399
|
+
relativePath: mappingRelativePath,
|
|
400
|
+
label: "classification mapping",
|
|
401
|
+
});
|
|
402
|
+
mappingFile = parseYaml(new TextDecoder("utf-8", { fatal: true }).decode(mappingBytes));
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (error instanceof PcrClassificationMappingError) {
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
throw new PcrClassificationMappingError({
|
|
408
|
+
system,
|
|
409
|
+
version,
|
|
410
|
+
code,
|
|
411
|
+
issue:
|
|
412
|
+
`canonical mapping source ${mappingRelativePath} could not be read safely ` +
|
|
413
|
+
`(${error instanceof Error ? error.message : String(error)})`,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
assertCurrentAcceptedMappingDocument({ mappingFile, system, version, code });
|
|
417
|
+
const matches = (mappingFile.mappings ?? []).filter(
|
|
418
|
+
(entry) => String(entry.code) === String(code),
|
|
419
|
+
);
|
|
420
|
+
if (matches.length === 0) {
|
|
421
|
+
throw new Error(`No PCR mapping found for ${system}:${version}:${code}`);
|
|
422
|
+
}
|
|
423
|
+
if (matches.length !== 1) {
|
|
424
|
+
throw new PcrClassificationMappingError({
|
|
425
|
+
system,
|
|
426
|
+
version,
|
|
427
|
+
code,
|
|
428
|
+
issue: `expected one selected edge, found ${matches.length}`,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
const mapping = matches[0];
|
|
432
|
+
if (
|
|
433
|
+
!CLASSIFICATION_MAPPING_RELATIONS.has(mapping.mapping_type)
|
|
434
|
+
|| mapping.mapping_type === "manual_review"
|
|
435
|
+
) {
|
|
436
|
+
throw new PcrClassificationMappingError({
|
|
437
|
+
system,
|
|
438
|
+
version,
|
|
439
|
+
code,
|
|
440
|
+
mappingType: mapping.mapping_type,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
assertAcceptedMappingDecision({ mapping, system, version, code });
|
|
444
|
+
return { mappingFile, mapping };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function assertCurrentAcceptedMappingDocument({ mappingFile, system, version, code }) {
|
|
448
|
+
if (mappingFile?.schema_version !== 2 || mappingFile?.status !== "current") {
|
|
449
|
+
throw new PcrClassificationMappingError({
|
|
450
|
+
system,
|
|
451
|
+
version,
|
|
452
|
+
code,
|
|
453
|
+
issue:
|
|
454
|
+
`expected schema_version 2 with status current, found ` +
|
|
455
|
+
`${String(mappingFile?.schema_version)}/${String(mappingFile?.status)}`,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (
|
|
459
|
+
String(mappingFile.classification_system).toLowerCase() !== String(system).toLowerCase()
|
|
460
|
+
|| String(mappingFile.classification_version) !== String(version)
|
|
461
|
+
|| !Array.isArray(mappingFile.mappings)
|
|
462
|
+
) {
|
|
463
|
+
throw new PcrClassificationMappingError({
|
|
464
|
+
system,
|
|
465
|
+
version,
|
|
466
|
+
code,
|
|
467
|
+
issue: "mapping document coordinate or mappings array does not match the requested classification",
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function assertAcceptedMappingDecision({ mapping, system, version, code }) {
|
|
473
|
+
const acceptance = mapping?.acceptance;
|
|
474
|
+
if (
|
|
475
|
+
acceptance?.status !== "accepted"
|
|
476
|
+
|| typeof acceptance?.decided_by !== "string"
|
|
477
|
+
|| acceptance.decided_by.trim().length === 0
|
|
478
|
+
|| !isRealCanonicalUtcTimestamp(acceptance?.decided_at_utc)
|
|
479
|
+
|| typeof acceptance?.decision_ref !== "string"
|
|
480
|
+
|| !/^(?:docs|classifications\/migrations)\/[A-Za-z0-9][A-Za-z0-9._/-]*\.(?:md|ya?ml|json)(?:#[A-Za-z0-9][A-Za-z0-9._-]*)?$/u.test(
|
|
481
|
+
acceptance.decision_ref,
|
|
482
|
+
)
|
|
483
|
+
) {
|
|
484
|
+
throw new PcrClassificationMappingError({
|
|
485
|
+
system,
|
|
486
|
+
version,
|
|
487
|
+
code,
|
|
488
|
+
mappingType: mapping?.mapping_type,
|
|
489
|
+
issue:
|
|
490
|
+
"selected edge requires acceptance.status=accepted, decided_by, strict decided_at_utc, and decision_ref",
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function isRealCanonicalUtcTimestamp(value) {
|
|
496
|
+
const match = typeof value === "string"
|
|
497
|
+
? /^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.[0-9]+)?Z$/u.exec(value)
|
|
498
|
+
: null;
|
|
499
|
+
if (!match) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
const [, year, month, day, hour, minute, second] = match.map(Number);
|
|
503
|
+
const parsed = new Date(0);
|
|
504
|
+
parsed.setUTCFullYear(year, month - 1, day);
|
|
505
|
+
parsed.setUTCHours(hour, minute, second, 0);
|
|
506
|
+
return (
|
|
507
|
+
parsed.getUTCFullYear() === year
|
|
508
|
+
&& parsed.getUTCMonth() === month - 1
|
|
509
|
+
&& parsed.getUTCDate() === day
|
|
510
|
+
&& parsed.getUTCHours() === hour
|
|
511
|
+
&& parsed.getUTCMinutes() === minute
|
|
512
|
+
&& parsed.getUTCSeconds() === second
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function assertCoverageMappingMatchesCanonical({ coverage, canonical, system, version, code }) {
|
|
517
|
+
const coverageMapping = coverage.mapping;
|
|
518
|
+
const canonicalMapping = canonical.mapping;
|
|
519
|
+
for (const field of ["pcr_id", "mapping_type", "confidence"]) {
|
|
520
|
+
if (String(coverageMapping?.[field]) !== String(canonicalMapping?.[field])) {
|
|
521
|
+
throw invalidCoverageResolution({
|
|
522
|
+
system,
|
|
523
|
+
version,
|
|
524
|
+
code,
|
|
525
|
+
issue: `coverage mapping ${field} ${String(coverageMapping?.[field])} does not match canonical mapping ${String(canonicalMapping?.[field])}`,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
for (const field of ["status", "decided_by", "decided_at_utc", "decision_ref"]) {
|
|
530
|
+
if (
|
|
531
|
+
String(coverageMapping?.acceptance?.[field])
|
|
532
|
+
!== String(canonicalMapping?.acceptance?.[field])
|
|
533
|
+
) {
|
|
534
|
+
throw invalidCoverageResolution({
|
|
535
|
+
system,
|
|
536
|
+
version,
|
|
537
|
+
code,
|
|
538
|
+
issue:
|
|
539
|
+
`coverage mapping acceptance.${field} ${String(coverageMapping?.acceptance?.[field])} ` +
|
|
540
|
+
`does not match canonical mapping ${String(canonicalMapping?.acceptance?.[field])}`,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function invalidCoverageResolution({ system, version, code, issue }) {
|
|
547
|
+
return new PcrClassificationCoverageSemanticError({
|
|
548
|
+
system,
|
|
549
|
+
version,
|
|
550
|
+
source: `classifications/indexes/${system}-${version}-coverage.json`,
|
|
551
|
+
issues: [`entry ${code}: ${issue}`],
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Resolve a PCR identity without silently following a retired id. Alias
|
|
557
|
+
* lookup intentionally happens before catalog lookup, including while the
|
|
558
|
+
* old scaffold directory still exists during a staged migration.
|
|
559
|
+
*/
|
|
560
|
+
export function resolvePcrIdentity({ root, pcrId, context = null }) {
|
|
561
|
+
const readRoot = root ?? context?.root;
|
|
562
|
+
const normalizedPcrId = String(pcrId);
|
|
563
|
+
const alias = findPcrIdAlias({ root: readRoot, pcrId: normalizedPcrId, context });
|
|
564
|
+
if (alias) {
|
|
565
|
+
return {
|
|
566
|
+
resolution_status: "legacy_id_redirect",
|
|
567
|
+
requested_pcr_id: normalizedPcrId,
|
|
568
|
+
redirect: legacyPcrIdRedirect(alias),
|
|
569
|
+
pcr: null,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
resolution_status: "canonical",
|
|
574
|
+
requested_pcr_id: normalizedPcrId,
|
|
575
|
+
redirect: null,
|
|
576
|
+
pcr: getCurrentPcrSnapshotUnchecked({ root: readRoot, pcrId: normalizedPcrId, context }).pcr,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function legacyPcrIdRedirect(alias) {
|
|
581
|
+
return {
|
|
582
|
+
source_pcr_id: alias.source_pcr_id,
|
|
583
|
+
source_pcr_path: alias.source_pcr_path,
|
|
584
|
+
target: structuredClone(alias.target),
|
|
585
|
+
reason: alias.reason,
|
|
586
|
+
decision_ref: alias.decision_ref,
|
|
587
|
+
next_command: nextCommandForLegacyPcrAlias(alias.target),
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function nextCommandForLegacyPcrAlias(target) {
|
|
592
|
+
if (target.kind === "classification_coverage") {
|
|
593
|
+
return (
|
|
594
|
+
"npm --silent run tiangong-pcr -- resolve --classification " +
|
|
595
|
+
`${target.classification_system}:${target.classification_version}:${target.code} --format json`
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
return (
|
|
599
|
+
"npm --silent run tiangong-pcr -- resolve --pcr " +
|
|
600
|
+
`${target.pcr_id} --format json`
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function throwIfLegacyPcrId({ root, pcrId, context = null }) {
|
|
605
|
+
const alias = findPcrIdAlias({ root, pcrId: String(pcrId), context });
|
|
606
|
+
if (alias) {
|
|
607
|
+
throw new PcrLegacyIdRedirectError(alias);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export function getPcrById({ root, pcrId, refresh = false, context = null }) {
|
|
612
|
+
return getCurrentPcrSnapshot({ root: root ?? context?.root, pcrId, refresh, context }).pcr;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function getPcrReadiness({ root, pcrId, refresh = false, context = null }) {
|
|
616
|
+
return structuredClone(getPcrById({ root: root ?? context?.root, pcrId, refresh, context }).readiness);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function readPcrMarkdown({ root, pcrId, language = "en-US", context = null }) {
|
|
620
|
+
const readRoot = root ?? context?.root;
|
|
621
|
+
const snapshot = getCurrentPcrSnapshot({ root: readRoot, pcrId, context });
|
|
622
|
+
if (pcrSource(readRoot) && !pcrSource(readRoot).languages.includes(language)) {
|
|
623
|
+
throw Object.assign(new Error(`Offline library contains English only; requested ${language}. Use --lang en-US.`), { code: "PCR_LIBRARY_LANGUAGE_UNAVAILABLE" });
|
|
624
|
+
}
|
|
625
|
+
const markdownName = `pcr.${language}.md`;
|
|
626
|
+
const markdownPath = path.join(readRoot, snapshot.pcr.path, markdownName);
|
|
627
|
+
const artifact = snapshot.artifacts[markdownName];
|
|
628
|
+
if (!artifact?.bytes) {
|
|
629
|
+
throw new Error(`PCR Markdown not found: ${toPosix(path.relative(readRoot, markdownPath))}`);
|
|
630
|
+
}
|
|
631
|
+
return artifact.bytes.toString("utf8");
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** A complete current document, read once through the same integrity boundary as guidance. */
|
|
635
|
+
export function readPcrDocumentBundle({ root, pcrId, context = null }) {
|
|
636
|
+
root = root ?? context?.root;
|
|
637
|
+
const snapshot = getCurrentPcrSnapshot({ root, pcrId, context });
|
|
638
|
+
const { pcr, manifest, manifestBytes, structured } = snapshot;
|
|
639
|
+
assertPcrUsable({ pcr, operation: "guidance" });
|
|
640
|
+
const languages = pcrSource(root)?.languages ?? declaredPcrLanguages(manifest);
|
|
641
|
+
if (![1, 2].includes(manifest.schema_version)) throw new Error(`Unsupported document manifest schema: ${manifest.schema_version}`);
|
|
642
|
+
for (const language of languages) if (typeof manifest.title?.[language] !== "string" || !manifest.title[language].trim()) throw new Error(`Document requires a nonempty ${language} title.`);
|
|
643
|
+
const artifacts = {};
|
|
644
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
645
|
+
for (const name of ["manifest.yaml", ...(pcrSource(root) ? [...languages.map((language) => `pcr.${language}.md`), "structured.yaml"] : pcrArtifactFiles(manifest))]) {
|
|
646
|
+
const bytes = name === "manifest.yaml" ? manifestBytes : snapshot.artifacts[name]?.bytes;
|
|
647
|
+
if (!bytes) throw new Error(`PCR document artifact is unavailable: ${pcr.path}/${name}`);
|
|
648
|
+
// A copy prevents consumers from changing a cached snapshot through Buffer aliasing.
|
|
649
|
+
const copy = Buffer.from(bytes);
|
|
650
|
+
const text = decoder.decode(copy);
|
|
651
|
+
if (!text.trim()) throw new Error(`PCR document artifact is empty: ${pcr.path}/${name}`);
|
|
652
|
+
artifacts[name] = {
|
|
653
|
+
path: `${pcr.path}/${name}`,
|
|
654
|
+
bytes: copy,
|
|
655
|
+
text,
|
|
656
|
+
sha256: exactByteSha256(copy),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
schema_version: 1,
|
|
661
|
+
workspace: "current",
|
|
662
|
+
pcr: structuredClone(pcr),
|
|
663
|
+
manifest: structuredClone(manifest),
|
|
664
|
+
structured: structuredClone(structured),
|
|
665
|
+
languages,
|
|
666
|
+
artifacts,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** Complete referenced legacy module source; scaffold status remains explicit. */
|
|
671
|
+
export function readPcrModuleDocumentBundle({ root, group, moduleId }) {
|
|
672
|
+
for (const value of [group, moduleId]) if (typeof value !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(value)) throw new Error("Invalid PCR module identity.");
|
|
673
|
+
const relativePath = `library/modules/${group}/${moduleId}.md`;
|
|
674
|
+
const filePath = path.join(root, relativePath);
|
|
675
|
+
const read = () => pcrSource(root)?.readFile(relativePath) ?? readControlledRepositoryFileBytes({ root, filePath, relativePath, label: "PCR module" });
|
|
676
|
+
const bytes = read();
|
|
677
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
678
|
+
const envelope = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(text);
|
|
679
|
+
if (!envelope) throw new Error(`Module requires frontmatter: ${relativePath}`);
|
|
680
|
+
const frontmatter = parseYaml(envelope[1]);
|
|
681
|
+
if (frontmatter.id !== `module.${group}.${moduleId}` || frontmatter.module_type !== group) throw new Error(`Module identity mismatch: ${relativePath}`);
|
|
682
|
+
if (!text.slice(envelope[0].length).trim()) throw new Error(`Module body is empty: ${relativePath}`);
|
|
683
|
+
if (!bytes.equals(read())) throw new Error(`Module changed during read: ${relativePath}`);
|
|
684
|
+
return { frontmatter, language: frontmatter.language ?? "en-US", artifact: { path: relativePath, bytes: Buffer.from(bytes), text, sha256: exactByteSha256(bytes) } };
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
export function buildGuidance({ root, pcrId, context = null }) {
|
|
688
|
+
return buildGuidanceForOperation({ root: root ?? context?.root, pcrId, operation: "guidance", context });
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
export function getVerifiedPcrProjection({ root, pcrId, context = null }) {
|
|
692
|
+
root = root ?? context?.root;
|
|
693
|
+
const snapshot = getCurrentPcrSnapshot({ root, pcrId, context });
|
|
694
|
+
const { pcr, structured, structuredPath } = snapshot;
|
|
695
|
+
assertPcrUsable({ pcr, operation: "guidance" });
|
|
696
|
+
if (!structured) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`PCR ${pcrId} passed readiness without a verified structured projection.`,
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
pcr: structuredClone(pcr),
|
|
703
|
+
readiness: structuredClone(pcr.readiness),
|
|
704
|
+
source_structured: toPosix(path.relative(root, structuredPath)),
|
|
705
|
+
structured: structuredClone(structured),
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function buildGuidanceForOperation({ root, pcrId, operation, context = null }) {
|
|
710
|
+
const snapshot = getCurrentPcrSnapshot({ root, pcrId, context });
|
|
711
|
+
const { pcr, structured, structuredPath } = snapshot;
|
|
712
|
+
assertPcrUsable({ pcr, operation });
|
|
713
|
+
if (!structured) {
|
|
714
|
+
throw new Error(
|
|
715
|
+
`PCR ${pcrId} passed readiness without a verified structured projection.`,
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
const guidance = {
|
|
719
|
+
schema_version: 1,
|
|
720
|
+
guidance_kind: "tiangong-pcr-agent-guidance",
|
|
721
|
+
pcr,
|
|
722
|
+
readiness: structuredClone(pcr.readiness),
|
|
723
|
+
source_structured: toPosix(path.relative(root, structuredPath)),
|
|
724
|
+
system_boundary: structured.system_boundary ?? {},
|
|
725
|
+
reference_flow: structured.reference_flow_definition ?? {},
|
|
726
|
+
boundary_abstraction: structured.boundary_abstraction ?? {},
|
|
727
|
+
measurement_rules: structured.measurement_rules ?? structured.unit_conventions ?? [],
|
|
728
|
+
process_map: structured.process_map ?? [],
|
|
729
|
+
process_inventory: structured.process_inventory ?? [],
|
|
730
|
+
production_guidance: {
|
|
731
|
+
collection_protocols: structured.dataset_production?.collection_protocols ?? [],
|
|
732
|
+
calculation_rules: structured.dataset_production?.calculation_rules ?? [],
|
|
733
|
+
data_quality_requirements: structured.dataset_production?.data_quality_requirements ?? [],
|
|
734
|
+
},
|
|
735
|
+
published_dataset_profile: structured.published_dataset_profile ?? {},
|
|
736
|
+
allocation_rules: structured.allocation_rules ?? [],
|
|
737
|
+
data_quality_rules: structured.data_quality_rules ?? [],
|
|
738
|
+
validation_rules: structured.validation_rules ?? [],
|
|
739
|
+
data_sources: structured.data_sources ?? [],
|
|
740
|
+
validation_notes: [
|
|
741
|
+
"Use this guidance as the source of Tiangong foreground data collection package requirements.",
|
|
742
|
+
"Preserve Tiangong UUIDs exactly and keep PCR-derived UUID references version-free.",
|
|
743
|
+
"Run tiangong-pcr validate-dataset after constructing a foreground data package and draft feedback if PCR guidance is missing or ambiguous.",
|
|
744
|
+
],
|
|
745
|
+
};
|
|
746
|
+
assertCoreContract("guidance-output.schema.json", guidance, {
|
|
747
|
+
code: "PCR_INTERNAL_CONTRACT_INVALID",
|
|
748
|
+
entityKind: "Agent guidance output",
|
|
749
|
+
source: toPosix(path.relative(root, structuredPath)),
|
|
750
|
+
});
|
|
751
|
+
return guidance;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export function createFeedbackDraft({
|
|
755
|
+
root,
|
|
756
|
+
pcrId,
|
|
757
|
+
type,
|
|
758
|
+
affectedSection = "",
|
|
759
|
+
processId = "",
|
|
760
|
+
flowRole = "",
|
|
761
|
+
summary = "",
|
|
762
|
+
evidence = "",
|
|
763
|
+
proposedChange = "",
|
|
764
|
+
agent = "tiangong-pcr",
|
|
765
|
+
}) {
|
|
766
|
+
if (!FEEDBACK_TYPES.includes(type)) {
|
|
767
|
+
throw new Error(`Unsupported feedback type: ${type}`);
|
|
768
|
+
}
|
|
769
|
+
const pcr = pcrId ? getPcrById({ root, pcrId }) : null;
|
|
770
|
+
const title = `PCR feedback: ${type}${pcrId ? ` for ${pcrId}` : ""}`;
|
|
771
|
+
const body = `## Summary
|
|
772
|
+
|
|
773
|
+
${summary || "Describe the PCR issue or improvement."}
|
|
774
|
+
|
|
775
|
+
## Feedback metadata
|
|
776
|
+
|
|
777
|
+
| Field | Value |
|
|
778
|
+
| --- | --- |
|
|
779
|
+
| PCR id | ${pcrId || ""} |
|
|
780
|
+
| PCR version | ${pcr?.version ?? ""} |
|
|
781
|
+
| Feedback type | ${type} |
|
|
782
|
+
| Affected section | ${affectedSection} |
|
|
783
|
+
| Affected process_id | ${processId} |
|
|
784
|
+
| Affected flow role | ${flowRole} |
|
|
785
|
+
| Generated by | ${agent} |
|
|
786
|
+
|
|
787
|
+
## Current PCR text or rule excerpt
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
## Proposed change
|
|
791
|
+
|
|
792
|
+
${proposedChange}
|
|
793
|
+
|
|
794
|
+
## Evidence sources
|
|
795
|
+
|
|
796
|
+
${evidence}
|
|
797
|
+
|
|
798
|
+
## Impact on foreground data package construction
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
## Maintainer intake checklist
|
|
802
|
+
|
|
803
|
+
- [ ] Classify the feedback as PCR content, classification mapping, UUID identity, translation, source evidence, or CLI/validator behavior.
|
|
804
|
+
- [ ] Verify cited evidence and Tiangong UUID references before changing PCR content.
|
|
805
|
+
- [ ] Update canonical \`pcr.en-US.md\` first when methodology changes.
|
|
806
|
+
- [ ] Align \`pcr.zh-CN.md\` when user-facing text changes.
|
|
807
|
+
- [ ] Run \`npm run pcr:sync-structured -- --pcr <library/pcrs/...>\` when canonical Markdown changes.
|
|
808
|
+
- [ ] Bump or publish the PCR manifest if lifecycle state changes.
|
|
809
|
+
`;
|
|
810
|
+
|
|
811
|
+
const draft = { title, body };
|
|
812
|
+
assertCoreContract("feedback-draft-output.schema.json", draft, {
|
|
813
|
+
code: "PCR_INTERNAL_CONTRACT_INVALID",
|
|
814
|
+
entityKind: "PCR feedback issue draft output",
|
|
815
|
+
});
|
|
816
|
+
return draft;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export function validateModelAgainstGuidance({ root, pcrId, model }) {
|
|
820
|
+
const guidance = buildGuidanceForOperation({ root, pcrId, operation: "validation" });
|
|
821
|
+
const findings = [];
|
|
822
|
+
const accepted = validateCoreContract("model-validation-input.schema.json", model).valid;
|
|
823
|
+
const text = typeof model === "string" ? model : accepted ? JSON.stringify(model) : "";
|
|
824
|
+
const checksPerformed = [];
|
|
825
|
+
const checksSkipped = [];
|
|
826
|
+
|
|
827
|
+
if (!accepted) {
|
|
828
|
+
findings.push({
|
|
829
|
+
severity: "error",
|
|
830
|
+
code: "invalid_model_input",
|
|
831
|
+
message: "Model input must be text or a JSON object.",
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const requiredQualifiers = asArray(guidance.reference_flow.required_qualifiers);
|
|
836
|
+
if (accepted && requiredQualifiers.length > 0) {
|
|
837
|
+
checksPerformed.push(performedCheck(
|
|
838
|
+
"required_qualifier_presence",
|
|
839
|
+
"reference_flow.required_qualifiers",
|
|
840
|
+
requiredQualifiers.length,
|
|
841
|
+
));
|
|
842
|
+
for (const qualifier of requiredQualifiers) {
|
|
843
|
+
if (!text.toLowerCase().includes(String(qualifier).toLowerCase())) {
|
|
844
|
+
findings.push({
|
|
845
|
+
severity: "warning",
|
|
846
|
+
code: "missing_required_qualifier",
|
|
847
|
+
message: `Model text does not mention required qualifier: ${qualifier}`,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if (accepted) {
|
|
854
|
+
addSkippedCheck(
|
|
855
|
+
checksSkipped,
|
|
856
|
+
"system_boundary_rules",
|
|
857
|
+
"system_boundary.rules",
|
|
858
|
+
asArray(guidance.system_boundary?.rules).length,
|
|
859
|
+
);
|
|
860
|
+
addSkippedCheck(
|
|
861
|
+
checksSkipped,
|
|
862
|
+
"boundary_abstraction",
|
|
863
|
+
"boundary_abstraction",
|
|
864
|
+
topLevelRequirementCount(guidance.boundary_abstraction),
|
|
865
|
+
);
|
|
866
|
+
addSkippedCheck(
|
|
867
|
+
checksSkipped,
|
|
868
|
+
"reference_flow_definition",
|
|
869
|
+
"reference_flow",
|
|
870
|
+
topLevelRequirementCount(guidance.reference_flow, ["required_qualifiers"]),
|
|
871
|
+
);
|
|
872
|
+
addSkippedCheck(checksSkipped, "measurement_rules", "measurement_rules", asArray(guidance.measurement_rules).length);
|
|
873
|
+
addSkippedCheck(checksSkipped, "process_map", "process_map", asArray(guidance.process_map).length);
|
|
874
|
+
addSkippedCheck(checksSkipped, "process_inventory", "process_inventory", countInventoryRows(guidance.process_inventory));
|
|
875
|
+
addSkippedCheck(
|
|
876
|
+
checksSkipped,
|
|
877
|
+
"collection_protocols",
|
|
878
|
+
"production_guidance.collection_protocols",
|
|
879
|
+
asArray(guidance.production_guidance.collection_protocols).length,
|
|
880
|
+
);
|
|
881
|
+
addSkippedCheck(
|
|
882
|
+
checksSkipped,
|
|
883
|
+
"calculation_rules",
|
|
884
|
+
"production_guidance.calculation_rules",
|
|
885
|
+
asArray(guidance.production_guidance.calculation_rules).length,
|
|
886
|
+
);
|
|
887
|
+
addSkippedCheck(
|
|
888
|
+
checksSkipped,
|
|
889
|
+
"data_quality_requirements",
|
|
890
|
+
"production_guidance.data_quality_requirements",
|
|
891
|
+
asArray(guidance.production_guidance.data_quality_requirements).length,
|
|
892
|
+
);
|
|
893
|
+
addSkippedCheck(checksSkipped, "data_quality_rules", "data_quality_rules", asArray(guidance.data_quality_rules).length);
|
|
894
|
+
addSkippedCheck(checksSkipped, "allocation_rules", "allocation_rules", asArray(guidance.allocation_rules).length);
|
|
895
|
+
addSkippedCheck(checksSkipped, "validation_rules", "validation_rules", asArray(guidance.validation_rules).length);
|
|
896
|
+
addSkippedCheck(
|
|
897
|
+
checksSkipped,
|
|
898
|
+
"published_dataset_profile",
|
|
899
|
+
"published_dataset_profile",
|
|
900
|
+
topLevelRequirementCount(guidance.published_dataset_profile),
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return buildValidationReport({
|
|
905
|
+
validationKind: "tiangong-pcr-model-validation",
|
|
906
|
+
guidance,
|
|
907
|
+
input: {
|
|
908
|
+
input_kind: "process_or_lifecyclemodel",
|
|
909
|
+
representation: typeof model === "string" ? "text" : Array.isArray(model) ? "array" : typeof model,
|
|
910
|
+
accepted,
|
|
911
|
+
},
|
|
912
|
+
findings,
|
|
913
|
+
checksPerformed,
|
|
914
|
+
checksSkipped,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
export function validateDatasetAgainstGuidance({ root, pcrId, dataset }) {
|
|
919
|
+
const guidance = buildGuidanceForOperation({ root, pcrId, operation: "validation" });
|
|
920
|
+
const findings = [];
|
|
921
|
+
const accepted = validateCoreContract("dataset-validation-input.schema.json", dataset).valid;
|
|
922
|
+
const checksPerformed = [];
|
|
923
|
+
const checksSkipped = [];
|
|
924
|
+
const records = accepted ? collectionRecordArrays(dataset) : [];
|
|
925
|
+
const requiredProtocolIds = asArray(guidance.production_guidance.collection_protocols)
|
|
926
|
+
.map((protocol) => protocol.protocol_id)
|
|
927
|
+
.filter(Boolean);
|
|
928
|
+
const presentProtocolIds = accepted ? collectCollectionRecordProtocolIds(dataset) : new Set();
|
|
929
|
+
|
|
930
|
+
if (!accepted) {
|
|
931
|
+
findings.push({
|
|
932
|
+
severity: "error",
|
|
933
|
+
code: "invalid_dataset_input",
|
|
934
|
+
message: "Foreground data package input must be a JSON object.",
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (accepted && requiredProtocolIds.length > 0) {
|
|
939
|
+
checksPerformed.push(performedCheck(
|
|
940
|
+
"collection_protocol_presence",
|
|
941
|
+
"production_guidance.collection_protocols",
|
|
942
|
+
requiredProtocolIds.length,
|
|
943
|
+
));
|
|
944
|
+
for (const protocolId of requiredProtocolIds) {
|
|
945
|
+
if (!presentProtocolIds.has(String(protocolId))) {
|
|
946
|
+
findings.push({
|
|
947
|
+
severity: "error",
|
|
948
|
+
code: "missing_collection_protocol_record",
|
|
949
|
+
message: `Foreground data package is missing collection record for protocol_id ${protocolId}`,
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (accepted) {
|
|
956
|
+
addSkippedCheck(
|
|
957
|
+
checksSkipped,
|
|
958
|
+
"system_boundary_rules",
|
|
959
|
+
"system_boundary.rules",
|
|
960
|
+
asArray(guidance.system_boundary?.rules).length,
|
|
961
|
+
);
|
|
962
|
+
addSkippedCheck(
|
|
963
|
+
checksSkipped,
|
|
964
|
+
"boundary_abstraction",
|
|
965
|
+
"boundary_abstraction",
|
|
966
|
+
topLevelRequirementCount(guidance.boundary_abstraction),
|
|
967
|
+
);
|
|
968
|
+
addSkippedCheck(
|
|
969
|
+
checksSkipped,
|
|
970
|
+
"reference_flow_rules",
|
|
971
|
+
"reference_flow",
|
|
972
|
+
topLevelRequirementCount(guidance.reference_flow, ["required_qualifiers"]) +
|
|
973
|
+
asArray(guidance.reference_flow?.required_qualifiers).length,
|
|
974
|
+
);
|
|
975
|
+
addSkippedCheck(checksSkipped, "measurement_rules", "measurement_rules", asArray(guidance.measurement_rules).length);
|
|
976
|
+
addSkippedCheck(checksSkipped, "process_map", "process_map", asArray(guidance.process_map).length);
|
|
977
|
+
addSkippedCheck(checksSkipped, "process_inventory", "process_inventory", countInventoryRows(guidance.process_inventory));
|
|
978
|
+
addSkippedCheck(
|
|
979
|
+
checksSkipped,
|
|
980
|
+
"calculation_rules",
|
|
981
|
+
"production_guidance.calculation_rules",
|
|
982
|
+
asArray(guidance.production_guidance.calculation_rules).length,
|
|
983
|
+
);
|
|
984
|
+
addSkippedCheck(
|
|
985
|
+
checksSkipped,
|
|
986
|
+
"data_quality_requirements",
|
|
987
|
+
"production_guidance.data_quality_requirements",
|
|
988
|
+
asArray(guidance.production_guidance.data_quality_requirements).length,
|
|
989
|
+
);
|
|
990
|
+
addSkippedCheck(checksSkipped, "data_quality_rules", "data_quality_rules", asArray(guidance.data_quality_rules).length);
|
|
991
|
+
addSkippedCheck(checksSkipped, "allocation_rules", "allocation_rules", asArray(guidance.allocation_rules).length);
|
|
992
|
+
addSkippedCheck(checksSkipped, "validation_rules", "validation_rules", asArray(guidance.validation_rules).length);
|
|
993
|
+
addSkippedCheck(
|
|
994
|
+
checksSkipped,
|
|
995
|
+
"published_dataset_profile",
|
|
996
|
+
"published_dataset_profile",
|
|
997
|
+
topLevelRequirementCount(guidance.published_dataset_profile),
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
return buildValidationReport({
|
|
1002
|
+
validationKind: "tiangong-pcr-dataset-validation",
|
|
1003
|
+
guidance,
|
|
1004
|
+
input: {
|
|
1005
|
+
input_kind: "foreground_data_package",
|
|
1006
|
+
representation: Array.isArray(dataset) ? "array" : dataset === null ? "null" : typeof dataset,
|
|
1007
|
+
accepted,
|
|
1008
|
+
collection_record_count: records.length,
|
|
1009
|
+
distinct_protocol_id_count: presentProtocolIds.size,
|
|
1010
|
+
},
|
|
1011
|
+
findings,
|
|
1012
|
+
checksPerformed,
|
|
1013
|
+
checksSkipped,
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function collectCollectionRecordProtocolIds(dataset) {
|
|
1018
|
+
if (typeof dataset === "string") {
|
|
1019
|
+
return new Set();
|
|
1020
|
+
}
|
|
1021
|
+
const ids = new Set();
|
|
1022
|
+
for (const record of collectionRecordArrays(dataset)) {
|
|
1023
|
+
const protocolId = record?.protocol_id ?? record?.collection_protocol_id;
|
|
1024
|
+
if (protocolId) {
|
|
1025
|
+
ids.add(String(protocolId));
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return ids;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function collectionRecordArrays(value) {
|
|
1032
|
+
if (!value || typeof value !== "object") {
|
|
1033
|
+
return [];
|
|
1034
|
+
}
|
|
1035
|
+
const records = [];
|
|
1036
|
+
for (const key of ["collection_records", "foreground_records", "measurement_records"]) {
|
|
1037
|
+
if (Array.isArray(value[key])) {
|
|
1038
|
+
records.push(...value[key]);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
if (value.data && typeof value.data === "object") {
|
|
1042
|
+
records.push(...collectionRecordArrays(value.data));
|
|
1043
|
+
}
|
|
1044
|
+
return records;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function currentPcrEntry(root, entry) {
|
|
1048
|
+
return currentPcrSnapshot(root, entry).pcr;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function getCurrentPcrSnapshot({ root, pcrId, refresh = false, context = null }) {
|
|
1052
|
+
throwIfLegacyPcrId({ root, pcrId, context });
|
|
1053
|
+
return getCurrentPcrSnapshotUnchecked({ root, pcrId, refresh, context });
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export function readPcrDistributionCatalog({ root }) {
|
|
1057
|
+
return readPcrCatalog(path.resolve(root));
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Builder-only: preserves legacy catalog records without following alias redirects.
|
|
1061
|
+
export function readPcrDistributionSnapshot(options) {
|
|
1062
|
+
return getCurrentPcrSnapshotUnchecked(options);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function getCurrentPcrSnapshotUnchecked({ root, pcrId, refresh = false, context = null }) {
|
|
1066
|
+
if (pcrSource(root)) {
|
|
1067
|
+
const entry = pcrSource(root).entry(pcrId);
|
|
1068
|
+
if (!entry) throw new Error(`PCR not found: ${pcrId}`);
|
|
1069
|
+
return currentPcrSnapshot(root, entry);
|
|
1070
|
+
}
|
|
1071
|
+
const normalizedRoot = path.resolve(root);
|
|
1072
|
+
if (context) {
|
|
1073
|
+
assertPcrReadContextFresh({ context, root: normalizedRoot });
|
|
1074
|
+
}
|
|
1075
|
+
const catalog = context
|
|
1076
|
+
? getPcrReadContextCatalog({
|
|
1077
|
+
context,
|
|
1078
|
+
root: normalizedRoot,
|
|
1079
|
+
readCatalog: readPcrCatalog,
|
|
1080
|
+
})
|
|
1081
|
+
: getPcrCatalog({ root: normalizedRoot, refresh });
|
|
1082
|
+
const entry = context
|
|
1083
|
+
? catalog.get(pcrId)
|
|
1084
|
+
: catalog.find((candidate) => candidate.id === pcrId);
|
|
1085
|
+
if (!entry) {
|
|
1086
|
+
throw new Error(`PCR not found: ${pcrId}`);
|
|
1087
|
+
}
|
|
1088
|
+
return currentPcrSnapshot(normalizedRoot, entry, context);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function currentPcrSnapshot(root, entry, context = null) {
|
|
1092
|
+
const snapshotFiles = pcrSource(root)?.snapshotFiles(entry) ?? readConsistentSnapshotFiles({ root, entry, context });
|
|
1093
|
+
const pcr = pcrFromManifest({
|
|
1094
|
+
root,
|
|
1095
|
+
pcrDir: path.dirname(entry.manifestPath),
|
|
1096
|
+
manifest: snapshotFiles.manifest,
|
|
1097
|
+
});
|
|
1098
|
+
let projection;
|
|
1099
|
+
try {
|
|
1100
|
+
projection = inspectPcrProjection({
|
|
1101
|
+
root,
|
|
1102
|
+
pcr,
|
|
1103
|
+
manifest: snapshotFiles.manifest,
|
|
1104
|
+
artifacts: snapshotFiles.artifacts,
|
|
1105
|
+
});
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
projection = failedProjectionInspection({ root, pcr, error });
|
|
1108
|
+
}
|
|
1109
|
+
pcr.readiness = assessPcrReadiness({
|
|
1110
|
+
pcr,
|
|
1111
|
+
projectionFingerprint: projection.fingerprint,
|
|
1112
|
+
structuredAvailable: projection.structuredAvailable,
|
|
1113
|
+
projectionCompletenessIssues: projection.completenessIssues,
|
|
1114
|
+
});
|
|
1115
|
+
return {
|
|
1116
|
+
pcr,
|
|
1117
|
+
manifest: snapshotFiles.manifest,
|
|
1118
|
+
manifestBytes: snapshotFiles.manifestBytes,
|
|
1119
|
+
artifacts: snapshotFiles.artifacts,
|
|
1120
|
+
...projection,
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function readConsistentSnapshotFiles({ root, entry, context = null }) {
|
|
1125
|
+
let lastFailure = null;
|
|
1126
|
+
for (let attempt = 1; attempt <= CURRENT_SNAPSHOT_MAX_ATTEMPTS; attempt += 1) {
|
|
1127
|
+
try {
|
|
1128
|
+
const locationA = assertCurrentSnapshotLocation({ root, entry });
|
|
1129
|
+
const manifestABytes = readCanonicalFileBytes(locationA.manifestPath);
|
|
1130
|
+
const manifest = parseYaml(manifestABytes.toString("utf8"));
|
|
1131
|
+
if (manifest.id !== entry.id) {
|
|
1132
|
+
throw snapshotAttemptError(
|
|
1133
|
+
"manifest_identity_changed",
|
|
1134
|
+
`Expected PCR id ${entry.id}, found ${String(manifest.id)}.`,
|
|
1135
|
+
{ expected_pcr_id: entry.id, actual_pcr_id: manifest.id ?? null },
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const managedMarkersA = currentManagedStateMarkers(locationA.pcrDir);
|
|
1140
|
+
assertManagedSnapshotHashesDeclared({ manifest, managedMarkers: managedMarkersA });
|
|
1141
|
+
const artifacts = Object.fromEntries(
|
|
1142
|
+
pcrArtifactFiles(manifest, { strict: false }).map((filename) => [
|
|
1143
|
+
filename,
|
|
1144
|
+
readObservedPcrArtifact({ root, context, pcrDir: locationA.pcrDir, filename }),
|
|
1145
|
+
]),
|
|
1146
|
+
);
|
|
1147
|
+
const releaseFailures = releaseArtifactFailures({ manifest, artifacts });
|
|
1148
|
+
const locationB = assertCurrentSnapshotLocation({ root, entry });
|
|
1149
|
+
const managedMarkersB = currentManagedStateMarkers(locationB.pcrDir);
|
|
1150
|
+
if (!sameStrings(managedMarkersA, managedMarkersB)) {
|
|
1151
|
+
throw snapshotAttemptError(
|
|
1152
|
+
"managed_state_changed_during_read",
|
|
1153
|
+
"PCR managed release state changed while its current snapshot was being read.",
|
|
1154
|
+
{
|
|
1155
|
+
managed_markers_before: managedMarkersA,
|
|
1156
|
+
managed_markers_after: managedMarkersB,
|
|
1157
|
+
},
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
const manifestBBytes = readCanonicalFileBytes(locationB.manifestPath);
|
|
1161
|
+
if (!manifestABytes.equals(manifestBBytes)) {
|
|
1162
|
+
throw snapshotAttemptError(
|
|
1163
|
+
"manifest_changed_during_read",
|
|
1164
|
+
"manifest.yaml changed while its current artifacts were being read.",
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
if (releaseFailures.length > 0) {
|
|
1168
|
+
throw snapshotAttemptError(
|
|
1169
|
+
"release_artifact_hash_mismatch",
|
|
1170
|
+
"Current PCR artifacts do not match manifest.release_artifacts.",
|
|
1171
|
+
{ manifest_status: manifest.status ?? null, artifacts: releaseFailures },
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
return { manifest, manifestBytes: manifestABytes, artifacts };
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
lastFailure = snapshotFailureDetails(error);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
throw new PcrCurrentSnapshotInconsistentError({
|
|
1180
|
+
pcrId: entry.id,
|
|
1181
|
+
pcrPath: entry.path,
|
|
1182
|
+
attempts: CURRENT_SNAPSHOT_MAX_ATTEMPTS,
|
|
1183
|
+
lastFailure,
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function readObservedPcrArtifact({ root, context, pcrDir, filename }) {
|
|
1188
|
+
const artifactPath = path.join(pcrDir, filename);
|
|
1189
|
+
if (context) {
|
|
1190
|
+
observePcrReadContextArtifactRead({
|
|
1191
|
+
context,
|
|
1192
|
+
root,
|
|
1193
|
+
relativePath: toPosix(path.relative(root, artifactPath)),
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
return readOptionalCanonicalFile(artifactPath);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function releaseArtifactFailures({ manifest, artifacts }) {
|
|
1200
|
+
if (!Object.hasOwn(manifest, "release_artifacts")) {
|
|
1201
|
+
return [];
|
|
1202
|
+
}
|
|
1203
|
+
const expectedHashes = expectedPcrArtifactHashes(manifest);
|
|
1204
|
+
const failures = [];
|
|
1205
|
+
for (const [filename, expected] of Object.entries(expectedHashes)) {
|
|
1206
|
+
const hashField = manifest.schema_version === 2 && filename !== "structured.yaml"
|
|
1207
|
+
? `markdown_sha256.${filename.slice(4, -3)}`
|
|
1208
|
+
: Object.entries(RELEASE_ARTIFACTS).find(([, file]) => file === filename)?.[0];
|
|
1209
|
+
const artifact = artifacts[filename];
|
|
1210
|
+
if (!artifact?.bytes) {
|
|
1211
|
+
failures.push({
|
|
1212
|
+
artifact: filename,
|
|
1213
|
+
hash_field: hashField,
|
|
1214
|
+
expected: typeof expected === "string" ? expected : null,
|
|
1215
|
+
actual: null,
|
|
1216
|
+
reason: `artifact_unreadable:${errorCode(artifact?.error)}`,
|
|
1217
|
+
});
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
const actual = exactByteSha256(artifact.bytes);
|
|
1221
|
+
if (typeof expected !== "string" || expected !== actual) {
|
|
1222
|
+
failures.push({
|
|
1223
|
+
artifact: filename,
|
|
1224
|
+
hash_field: hashField,
|
|
1225
|
+
expected: typeof expected === "string" ? expected : null,
|
|
1226
|
+
actual,
|
|
1227
|
+
reason: "sha256_mismatch",
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return failures;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function assertManagedSnapshotHashesDeclared({ manifest, managedMarkers }) {
|
|
1235
|
+
if (managedMarkers.length === 0 || Object.hasOwn(manifest, "release_artifacts")) {
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
throw snapshotAttemptError(
|
|
1239
|
+
"managed_release_artifacts_missing",
|
|
1240
|
+
"Managed PCR current state requires manifest.release_artifacts and cannot use legacy snapshot semantics.",
|
|
1241
|
+
{
|
|
1242
|
+
manifest_status: manifest.status ?? null,
|
|
1243
|
+
managed_markers: managedMarkers,
|
|
1244
|
+
required_field: "manifest.release_artifacts",
|
|
1245
|
+
required_hash_fields: Object.keys(RELEASE_ARTIFACTS),
|
|
1246
|
+
},
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function currentManagedStateMarkers(pcrDir) {
|
|
1251
|
+
return ["release-history.yaml", "releases"]
|
|
1252
|
+
.filter((name) => pathEntryExists(path.join(pcrDir, name)))
|
|
1253
|
+
.sort((left, right) => left.localeCompare(right));
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
function pathEntryExists(targetPath) {
|
|
1257
|
+
try {
|
|
1258
|
+
lstatSync(targetPath);
|
|
1259
|
+
return true;
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
if (error?.code === "ENOENT") {
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1264
|
+
throw error;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function sameStrings(left, right) {
|
|
1269
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function assertCurrentSnapshotLocation({ root, entry }) {
|
|
1273
|
+
const normalizedRoot = path.resolve(root);
|
|
1274
|
+
const pathSegments = String(entry.path).split("/");
|
|
1275
|
+
if (
|
|
1276
|
+
pathSegments.length !== 5 ||
|
|
1277
|
+
pathSegments[0] !== "library" ||
|
|
1278
|
+
pathSegments[1] !== "pcrs" ||
|
|
1279
|
+
pathSegments.slice(2).some((segment) => !segment || segment === "." || segment === "..")
|
|
1280
|
+
) {
|
|
1281
|
+
throw snapshotAttemptError(
|
|
1282
|
+
"canonical_pcr_path_invalid",
|
|
1283
|
+
"Cached PCR path is not a canonical domain/subdomain/leaf path.",
|
|
1284
|
+
{
|
|
1285
|
+
pcr_path: entry.path,
|
|
1286
|
+
reason: "non_canonical_relative_path",
|
|
1287
|
+
},
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
const directoryPaths = [normalizedRoot];
|
|
1292
|
+
for (const segment of pathSegments) {
|
|
1293
|
+
directoryPaths.push(path.join(directoryPaths.at(-1), segment));
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
let rootRealPath = null;
|
|
1297
|
+
for (const [index, directoryPath] of directoryPaths.entries()) {
|
|
1298
|
+
const relativePath = toPosix(path.relative(normalizedRoot, directoryPath)) || ".";
|
|
1299
|
+
let stat;
|
|
1300
|
+
try {
|
|
1301
|
+
stat = lstatSync(directoryPath);
|
|
1302
|
+
} catch (error) {
|
|
1303
|
+
throw snapshotAttemptError(
|
|
1304
|
+
"canonical_pcr_path_invalid",
|
|
1305
|
+
`Canonical PCR directory is unavailable: ${relativePath}.`,
|
|
1306
|
+
{
|
|
1307
|
+
path_component: relativePath,
|
|
1308
|
+
reason: `directory_unreadable:${errorCode(error)}`,
|
|
1309
|
+
expected_type: "regular_directory",
|
|
1310
|
+
},
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
1314
|
+
throw snapshotAttemptError(
|
|
1315
|
+
"canonical_pcr_path_invalid",
|
|
1316
|
+
`Canonical PCR path component is not a regular directory: ${relativePath}.`,
|
|
1317
|
+
{
|
|
1318
|
+
path_component: relativePath,
|
|
1319
|
+
reason: stat.isSymbolicLink() ? "symbolic_link" : "not_a_directory",
|
|
1320
|
+
expected_type: "regular_directory",
|
|
1321
|
+
},
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
let realPath;
|
|
1326
|
+
try {
|
|
1327
|
+
realPath = realpathSync(directoryPath);
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
throw snapshotAttemptError(
|
|
1330
|
+
"canonical_pcr_path_invalid",
|
|
1331
|
+
`Canonical PCR directory cannot be resolved: ${relativePath}.`,
|
|
1332
|
+
{
|
|
1333
|
+
path_component: relativePath,
|
|
1334
|
+
reason: `realpath_unavailable:${errorCode(error)}`,
|
|
1335
|
+
expected_type: "regular_directory",
|
|
1336
|
+
},
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
if (index === 0) {
|
|
1340
|
+
rootRealPath = realPath;
|
|
1341
|
+
} else if (!pathIsInside(rootRealPath, realPath)) {
|
|
1342
|
+
throw snapshotAttemptError(
|
|
1343
|
+
"canonical_pcr_path_invalid",
|
|
1344
|
+
`Canonical PCR directory resolves outside the repository root: ${relativePath}.`,
|
|
1345
|
+
{
|
|
1346
|
+
path_component: relativePath,
|
|
1347
|
+
reason: "realpath_outside_root",
|
|
1348
|
+
expected_type: "regular_directory_within_root",
|
|
1349
|
+
},
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
const pcrDir = directoryPaths.at(-1);
|
|
1355
|
+
const manifestPath = path.join(pcrDir, "manifest.yaml");
|
|
1356
|
+
if (path.resolve(entry.manifestPath) !== manifestPath) {
|
|
1357
|
+
throw snapshotAttemptError(
|
|
1358
|
+
"canonical_pcr_path_invalid",
|
|
1359
|
+
"Cached manifest path is not the direct manifest.yaml child of its canonical PCR leaf.",
|
|
1360
|
+
{
|
|
1361
|
+
pcr_path: entry.path,
|
|
1362
|
+
reason: "manifest_not_direct_leaf_child",
|
|
1363
|
+
},
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
return { manifestPath, pcrDir };
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function pathIsInside(rootPath, candidatePath) {
|
|
1370
|
+
const relativePath = path.relative(rootPath, candidatePath);
|
|
1371
|
+
return relativePath === "" || (!path.isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${path.sep}`));
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function exactByteSha256(bytes) {
|
|
1375
|
+
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
function readOptionalCanonicalFile(filePath) {
|
|
1379
|
+
try {
|
|
1380
|
+
return { bytes: readCanonicalFileBytes(filePath), error: null };
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
return { bytes: null, error };
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
function readControlledRepositoryFileBytes({ root, filePath, relativePath, label }) {
|
|
1387
|
+
const normalizedRoot = path.resolve(root);
|
|
1388
|
+
const normalizedFilePath = path.resolve(filePath);
|
|
1389
|
+
const containedPath = path.relative(normalizedRoot, normalizedFilePath);
|
|
1390
|
+
if (
|
|
1391
|
+
containedPath.length === 0
|
|
1392
|
+
|| path.isAbsolute(containedPath)
|
|
1393
|
+
|| containedPath === ".."
|
|
1394
|
+
|| containedPath.startsWith(`..${path.sep}`)
|
|
1395
|
+
) {
|
|
1396
|
+
throw new Error(`${label} path escapes the repository root: ${relativePath}`);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const expectedRelativePath = toPosix(containedPath);
|
|
1400
|
+
if (expectedRelativePath !== relativePath) {
|
|
1401
|
+
throw new Error(`${label} path is not canonical: ${relativePath}`);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
const before = assertControlledRepositoryPath({
|
|
1405
|
+
root: normalizedRoot,
|
|
1406
|
+
containedPath,
|
|
1407
|
+
relativePath,
|
|
1408
|
+
label,
|
|
1409
|
+
});
|
|
1410
|
+
let descriptor;
|
|
1411
|
+
try {
|
|
1412
|
+
descriptor = openSync(
|
|
1413
|
+
normalizedFilePath,
|
|
1414
|
+
fsConstants.O_RDONLY
|
|
1415
|
+
| (fsConstants.O_NOFOLLOW ?? 0)
|
|
1416
|
+
| (fsConstants.O_NONBLOCK ?? 0),
|
|
1417
|
+
);
|
|
1418
|
+
const opened = fstatSync(descriptor);
|
|
1419
|
+
if (!opened.isFile()) {
|
|
1420
|
+
throw new Error(`${label} is not a regular file: ${relativePath}`);
|
|
1421
|
+
}
|
|
1422
|
+
const after = assertControlledRepositoryPath({
|
|
1423
|
+
root: normalizedRoot,
|
|
1424
|
+
containedPath,
|
|
1425
|
+
relativePath,
|
|
1426
|
+
label,
|
|
1427
|
+
});
|
|
1428
|
+
if (
|
|
1429
|
+
opened.dev !== before.dev
|
|
1430
|
+
|| opened.ino !== before.ino
|
|
1431
|
+
|| opened.dev !== after.dev
|
|
1432
|
+
|| opened.ino !== after.ino
|
|
1433
|
+
) {
|
|
1434
|
+
throw new Error(`${label} path changed while it was being opened: ${relativePath}`);
|
|
1435
|
+
}
|
|
1436
|
+
return readFileSync(descriptor);
|
|
1437
|
+
} finally {
|
|
1438
|
+
if (descriptor !== undefined) {
|
|
1439
|
+
closeSync(descriptor);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
function assertControlledRepositoryPath({ root, containedPath, relativePath, label }) {
|
|
1445
|
+
let currentPath = root;
|
|
1446
|
+
let finalStats;
|
|
1447
|
+
const segments = containedPath.split(path.sep);
|
|
1448
|
+
for (const [index, segment] of segments.entries()) {
|
|
1449
|
+
currentPath = path.join(currentPath, segment);
|
|
1450
|
+
const stats = lstatSync(currentPath);
|
|
1451
|
+
if (stats.isSymbolicLink()) {
|
|
1452
|
+
throw new Error(`${label} path contains a symbolic link: ${relativePath}`);
|
|
1453
|
+
}
|
|
1454
|
+
const isLast = index === segments.length - 1;
|
|
1455
|
+
if (isLast ? !stats.isFile() : !stats.isDirectory()) {
|
|
1456
|
+
throw new Error(
|
|
1457
|
+
isLast
|
|
1458
|
+
? `${label} is not a regular file: ${relativePath}`
|
|
1459
|
+
: `${label} parent is not a directory: ${relativePath}`,
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
if (isLast) {
|
|
1463
|
+
finalStats = stats;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
const realRoot = realpathSync(root);
|
|
1468
|
+
const realFile = realpathSync(path.join(root, containedPath));
|
|
1469
|
+
if (!pathIsInside(realRoot, realFile) || realRoot === realFile) {
|
|
1470
|
+
throw new Error(`${label} real path escapes the repository root: ${relativePath}`);
|
|
1471
|
+
}
|
|
1472
|
+
return finalStats;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function readCanonicalFileBytes(filePath) {
|
|
1476
|
+
const stat = lstatSync(filePath);
|
|
1477
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
1478
|
+
const error = new Error(`Canonical PCR artifact is not a regular file: ${filePath}`);
|
|
1479
|
+
error.code = stat.isSymbolicLink()
|
|
1480
|
+
? "PCR_CANONICAL_SYMLINK_REJECTED"
|
|
1481
|
+
: "PCR_CANONICAL_FILE_NOT_REGULAR";
|
|
1482
|
+
throw error;
|
|
1483
|
+
}
|
|
1484
|
+
const descriptor = openSync(
|
|
1485
|
+
filePath,
|
|
1486
|
+
fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0),
|
|
1487
|
+
);
|
|
1488
|
+
try {
|
|
1489
|
+
if (!fstatSync(descriptor).isFile()) {
|
|
1490
|
+
const error = new Error(`Canonical PCR artifact is not a regular file: ${filePath}`);
|
|
1491
|
+
error.code = "PCR_CANONICAL_FILE_NOT_REGULAR";
|
|
1492
|
+
throw error;
|
|
1493
|
+
}
|
|
1494
|
+
return readFileSync(descriptor);
|
|
1495
|
+
} finally {
|
|
1496
|
+
closeSync(descriptor);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function snapshotAttemptError(code, message, details = {}) {
|
|
1501
|
+
const error = new Error(message);
|
|
1502
|
+
error.snapshotCode = code;
|
|
1503
|
+
error.snapshotDetails = details;
|
|
1504
|
+
return error;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
function snapshotFailureDetails(error) {
|
|
1508
|
+
return {
|
|
1509
|
+
code: error?.snapshotCode ?? errorCode(error),
|
|
1510
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1511
|
+
...(isRecord(error?.snapshotDetails) ? error.snapshotDetails : {}),
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
function pcrFromManifest({ root, pcrDir, manifest }) {
|
|
1516
|
+
const pcr = {
|
|
1517
|
+
id: manifest.id,
|
|
1518
|
+
path: toPosix(path.relative(root, pcrDir)),
|
|
1519
|
+
title: manifest.title ?? {},
|
|
1520
|
+
status: manifest.status ?? "unknown",
|
|
1521
|
+
version: manifest.version ?? null,
|
|
1522
|
+
content_maturity: manifest.content_maturity ?? null,
|
|
1523
|
+
languages: manifest.languages ?? {},
|
|
1524
|
+
translation_status: manifest.translation_status ?? {},
|
|
1525
|
+
classification_refs: manifest.classification_refs ?? [],
|
|
1526
|
+
};
|
|
1527
|
+
pcr.record_kind = recordKindForPcr(pcr);
|
|
1528
|
+
return pcr;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
function inspectPcrProjection({ root, pcr, manifest, artifacts }) {
|
|
1532
|
+
const pcrDir = path.join(root, pcr.path);
|
|
1533
|
+
const structuredPath = path.join(pcrDir, "structured.yaml");
|
|
1534
|
+
const structuredArtifact = artifacts["structured.yaml"];
|
|
1535
|
+
const markdownArtifact = artifacts["pcr.en-US.md"];
|
|
1536
|
+
if (!isMaterialPcr(pcr)) {
|
|
1537
|
+
return {
|
|
1538
|
+
fingerprint: {
|
|
1539
|
+
...projectionNotRequiredState(),
|
|
1540
|
+
schema_valid: null,
|
|
1541
|
+
},
|
|
1542
|
+
structured: null,
|
|
1543
|
+
structuredPath,
|
|
1544
|
+
structuredAvailable: Boolean(structuredArtifact?.bytes),
|
|
1545
|
+
completenessIssues: [],
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
if (!structuredArtifact?.bytes && structuredArtifact?.error?.code === "ENOENT") {
|
|
1550
|
+
return unavailableProjectionInspection({
|
|
1551
|
+
structuredPath,
|
|
1552
|
+
structuredAvailable: false,
|
|
1553
|
+
fingerprint: missingProjectionState(
|
|
1554
|
+
"missing",
|
|
1555
|
+
"structured_projection_missing",
|
|
1556
|
+
"structured.yaml is required for material PCR guidance and validation.",
|
|
1557
|
+
),
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
if (!markdownArtifact?.bytes && markdownArtifact?.error?.code === "ENOENT") {
|
|
1562
|
+
return unavailableProjectionInspection({
|
|
1563
|
+
structuredPath,
|
|
1564
|
+
structuredAvailable: Boolean(structuredArtifact?.bytes),
|
|
1565
|
+
fingerprint: missingProjectionState(
|
|
1566
|
+
"source_missing",
|
|
1567
|
+
"projection_source_missing",
|
|
1568
|
+
"Canonical pcr.en-US.md is required to verify the structured projection.",
|
|
1569
|
+
),
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
if (!markdownArtifact?.bytes) {
|
|
1574
|
+
return unavailableProjectionInspection({
|
|
1575
|
+
structuredPath,
|
|
1576
|
+
structuredAvailable: Boolean(structuredArtifact?.bytes),
|
|
1577
|
+
fingerprint: invalidProjectionState(
|
|
1578
|
+
"projection_source_unreadable",
|
|
1579
|
+
`Canonical pcr.en-US.md could not be read (${errorCode(markdownArtifact?.error)}).`,
|
|
1580
|
+
null,
|
|
1581
|
+
),
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
if (!structuredArtifact?.bytes) {
|
|
1586
|
+
return unavailableProjectionInspection({
|
|
1587
|
+
structuredPath,
|
|
1588
|
+
structuredAvailable: false,
|
|
1589
|
+
fingerprint: invalidProjectionState(
|
|
1590
|
+
"structured_projection_unreadable",
|
|
1591
|
+
`structured.yaml could not be read (${errorCode(structuredArtifact?.error)}).`,
|
|
1592
|
+
false,
|
|
1593
|
+
),
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
const sourceMarkdown = markdownArtifact.bytes.toString("utf8");
|
|
1598
|
+
const structuredText = structuredArtifact.bytes.toString("utf8");
|
|
1599
|
+
|
|
1600
|
+
let structured;
|
|
1601
|
+
try {
|
|
1602
|
+
structured = parseYaml(structuredText);
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
return unavailableProjectionInspection({
|
|
1605
|
+
structuredPath,
|
|
1606
|
+
structuredAvailable: true,
|
|
1607
|
+
fingerprint: invalidProjectionState(
|
|
1608
|
+
"structured_projection_parse_error",
|
|
1609
|
+
`structured.yaml could not be parsed (${errorCode(error)}).`,
|
|
1610
|
+
false,
|
|
1611
|
+
),
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const schemaResult = validateCoreContract(
|
|
1616
|
+
"structured-projection.schema.json",
|
|
1617
|
+
structured,
|
|
1618
|
+
{ entityKind: "material structured projection" },
|
|
1619
|
+
);
|
|
1620
|
+
const integrity = inspectProjectionIntegrity({
|
|
1621
|
+
sourceMarkdown,
|
|
1622
|
+
structuredText,
|
|
1623
|
+
metadata: structured?.projection_metadata,
|
|
1624
|
+
});
|
|
1625
|
+
const schemaIssues = schemaResult.valid
|
|
1626
|
+
? []
|
|
1627
|
+
: [
|
|
1628
|
+
{
|
|
1629
|
+
code: "structured_schema_invalid",
|
|
1630
|
+
message: `structured.yaml violates the material projection schema (${schemaResult.errors.length} issue(s)).`,
|
|
1631
|
+
},
|
|
1632
|
+
...schemaResult.errors.map((error) => ({
|
|
1633
|
+
code: `structured_schema.${error.keyword}`,
|
|
1634
|
+
message: `${error.instance_path}: ${error.message}`,
|
|
1635
|
+
})),
|
|
1636
|
+
];
|
|
1637
|
+
const issues = deduplicateMessages([...schemaIssues, ...integrity.issues]);
|
|
1638
|
+
const fingerprint = {
|
|
1639
|
+
...integrity,
|
|
1640
|
+
status: schemaResult.valid ? integrity.status : "invalid",
|
|
1641
|
+
schema_valid: schemaResult.valid,
|
|
1642
|
+
issues,
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1645
|
+
return {
|
|
1646
|
+
fingerprint,
|
|
1647
|
+
structured: schemaResult.valid && integrity.status === "current" ? structured : null,
|
|
1648
|
+
structuredPath,
|
|
1649
|
+
structuredAvailable: true,
|
|
1650
|
+
completenessIssues: schemaResult.valid
|
|
1651
|
+
? materialProjectionCompletenessIssues(structured, {
|
|
1652
|
+
expectedPcrId: pcr.id,
|
|
1653
|
+
allowUnresolvedProductFlowUuid:
|
|
1654
|
+
hasDeclaredUnresolvedReferenceProductFlow(structured, manifest),
|
|
1655
|
+
})
|
|
1656
|
+
: [],
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
function unavailableProjectionInspection({ fingerprint, structuredPath, structuredAvailable }) {
|
|
1661
|
+
return {
|
|
1662
|
+
fingerprint,
|
|
1663
|
+
structured: null,
|
|
1664
|
+
structuredPath,
|
|
1665
|
+
structuredAvailable,
|
|
1666
|
+
completenessIssues: [],
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
function failedProjectionInspection({ root, pcr, error }) {
|
|
1671
|
+
return unavailableProjectionInspection({
|
|
1672
|
+
structuredPath: path.join(root, pcr.path, "structured.yaml"),
|
|
1673
|
+
structuredAvailable: false,
|
|
1674
|
+
fingerprint: invalidProjectionState(
|
|
1675
|
+
"projection_inspection_failed",
|
|
1676
|
+
`Projection integrity inspection failed (${errorCode(error)}).`,
|
|
1677
|
+
false,
|
|
1678
|
+
),
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function invalidProjectionState(code, message, schemaValid) {
|
|
1683
|
+
return {
|
|
1684
|
+
required: true,
|
|
1685
|
+
status: "invalid",
|
|
1686
|
+
schema_valid: schemaValid,
|
|
1687
|
+
contract_version: null,
|
|
1688
|
+
source_sha256: null,
|
|
1689
|
+
generated_content_sha256: null,
|
|
1690
|
+
source_hash_valid: null,
|
|
1691
|
+
content_hash_valid: null,
|
|
1692
|
+
issues: [{ code, message }],
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
function errorCode(error) {
|
|
1697
|
+
return typeof error?.code === "string" && error.code
|
|
1698
|
+
? error.code
|
|
1699
|
+
: "unknown_error";
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function missingProjectionState(status, code, message) {
|
|
1703
|
+
return {
|
|
1704
|
+
required: true,
|
|
1705
|
+
status,
|
|
1706
|
+
schema_valid: null,
|
|
1707
|
+
contract_version: null,
|
|
1708
|
+
source_sha256: null,
|
|
1709
|
+
generated_content_sha256: null,
|
|
1710
|
+
source_hash_valid: null,
|
|
1711
|
+
content_hash_valid: null,
|
|
1712
|
+
issues: [{ code, message }],
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
function isMaterialPcr(pcr) {
|
|
1717
|
+
return recordKindForPcr(pcr) === "methodology";
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
function normalizeCatalogScope(scope) {
|
|
1721
|
+
const normalized = String(scope);
|
|
1722
|
+
if (!PCR_CATALOG_SCOPE_SET.has(normalized)) {
|
|
1723
|
+
throw new PcrCatalogScopeError(scope);
|
|
1724
|
+
}
|
|
1725
|
+
return normalized;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
function recordMatchesScope(pcr, scope) {
|
|
1729
|
+
if (scope === "all") {
|
|
1730
|
+
return true;
|
|
1731
|
+
}
|
|
1732
|
+
return scope === "material"
|
|
1733
|
+
? pcr.record_kind === "methodology"
|
|
1734
|
+
: pcr.record_kind === "legacy_scaffold_reference";
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
function currentCatalogEntryMatchesScope(root, entry, scope) {
|
|
1738
|
+
const manifest = readConsistentCurrentManifest({ root, entry });
|
|
1739
|
+
const recordKind = recordKindForPcr({
|
|
1740
|
+
status: manifest.status ?? "unknown",
|
|
1741
|
+
content_maturity: manifest.content_maturity ?? null,
|
|
1742
|
+
});
|
|
1743
|
+
return scope === "material"
|
|
1744
|
+
? recordKind === "methodology"
|
|
1745
|
+
: recordKind === "legacy_scaffold_reference";
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
function readConsistentCurrentManifest({ root, entry }) {
|
|
1749
|
+
let lastFailure = null;
|
|
1750
|
+
for (let attempt = 1; attempt <= CURRENT_SNAPSHOT_MAX_ATTEMPTS; attempt += 1) {
|
|
1751
|
+
try {
|
|
1752
|
+
const locationA = assertCurrentSnapshotLocation({ root, entry });
|
|
1753
|
+
const manifestABytes = readCanonicalFileBytes(locationA.manifestPath);
|
|
1754
|
+
const manifest = parseYaml(manifestABytes.toString("utf8"));
|
|
1755
|
+
if (manifest.id !== entry.id) {
|
|
1756
|
+
throw snapshotAttemptError(
|
|
1757
|
+
"manifest_identity_changed",
|
|
1758
|
+
`Expected PCR id ${entry.id}, found ${String(manifest.id)}.`,
|
|
1759
|
+
{ expected_pcr_id: entry.id, actual_pcr_id: manifest.id ?? null },
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
const locationB = assertCurrentSnapshotLocation({ root, entry });
|
|
1764
|
+
const manifestBBytes = readCanonicalFileBytes(locationB.manifestPath);
|
|
1765
|
+
if (!manifestABytes.equals(manifestBBytes)) {
|
|
1766
|
+
throw snapshotAttemptError(
|
|
1767
|
+
"manifest_changed_during_read",
|
|
1768
|
+
"manifest.yaml changed while its catalog scope was being determined.",
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
return manifest;
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
lastFailure = snapshotFailureDetails(error);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
throw new PcrCurrentSnapshotInconsistentError({
|
|
1777
|
+
pcrId: entry.id,
|
|
1778
|
+
pcrPath: entry.path,
|
|
1779
|
+
attempts: CURRENT_SNAPSHOT_MAX_ATTEMPTS,
|
|
1780
|
+
lastFailure,
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
function recordKindForPcr(pcr) {
|
|
1785
|
+
if (pcr.status === "scaffold" && pcr.content_maturity === "empty_scaffold") {
|
|
1786
|
+
return "legacy_scaffold_reference";
|
|
1787
|
+
}
|
|
1788
|
+
if (
|
|
1789
|
+
METHODOLOGY_LIFECYCLE_STATUSES.has(pcr.status) &&
|
|
1790
|
+
METHODOLOGY_MATURITIES.has(pcr.content_maturity)
|
|
1791
|
+
) {
|
|
1792
|
+
return "methodology";
|
|
1793
|
+
}
|
|
1794
|
+
return "invalid_lifecycle_state";
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
function assessPcrReadiness({
|
|
1798
|
+
pcr,
|
|
1799
|
+
projectionFingerprint,
|
|
1800
|
+
structuredAvailable,
|
|
1801
|
+
projectionCompletenessIssues = [],
|
|
1802
|
+
}) {
|
|
1803
|
+
const blockers = [];
|
|
1804
|
+
const warnings = [];
|
|
1805
|
+
const methodologyStatus = pcr.content_maturity ?? "unknown";
|
|
1806
|
+
const lifecycleStatus = pcr.status ?? "unknown";
|
|
1807
|
+
const chineseTranslationStatus = pcr.translation_status?.["zh-CN"] ?? "unknown";
|
|
1808
|
+
if (pcr.record_kind === "invalid_lifecycle_state") {
|
|
1809
|
+
blockers.push({
|
|
1810
|
+
code: "invalid_lifecycle_identity",
|
|
1811
|
+
message:
|
|
1812
|
+
`status ${lifecycleStatus} and content_maturity ${methodologyStatus} do not identify either ` +
|
|
1813
|
+
"a legacy scaffold or a material methodology record.",
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
if (!GUIDANCE_MATURITIES.has(methodologyStatus)) {
|
|
1817
|
+
blockers.push({
|
|
1818
|
+
code: "methodology_not_authored",
|
|
1819
|
+
message: `content_maturity ${methodologyStatus} is not usable methodology.`,
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
if (lifecycleStatus === "scaffold") {
|
|
1823
|
+
blockers.push({
|
|
1824
|
+
code: "scaffold_lifecycle",
|
|
1825
|
+
message: "PCR lifecycle status is scaffold.",
|
|
1826
|
+
});
|
|
1827
|
+
} else if (!GUIDANCE_LIFECYCLE_STATUSES.has(lifecycleStatus)) {
|
|
1828
|
+
blockers.push({
|
|
1829
|
+
code: "lifecycle_not_usable",
|
|
1830
|
+
message: `PCR lifecycle status ${lifecycleStatus} is not available for guidance.`,
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
if (lifecycleStatus === "deprecated" || methodologyStatus === "deprecated_methodology") {
|
|
1834
|
+
blockers.push({
|
|
1835
|
+
code: "deprecated_methodology",
|
|
1836
|
+
message: "Deprecated PCR methodology must not guide new work.",
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
if (projectionFingerprint.required && (
|
|
1840
|
+
projectionFingerprint.schema_valid !== true || projectionFingerprint.status !== "current"
|
|
1841
|
+
)) {
|
|
1842
|
+
blockers.push(...projectionFingerprint.issues);
|
|
1843
|
+
}
|
|
1844
|
+
if (
|
|
1845
|
+
projectionFingerprint.required &&
|
|
1846
|
+
projectionFingerprint.schema_valid === true &&
|
|
1847
|
+
projectionFingerprint.status === "current"
|
|
1848
|
+
) {
|
|
1849
|
+
blockers.push(...projectionCompletenessIssues);
|
|
1850
|
+
}
|
|
1851
|
+
if (
|
|
1852
|
+
GUIDANCE_MATURITIES.has(methodologyStatus) &&
|
|
1853
|
+
!USABLE_LIFECYCLE_STATES.get(lifecycleStatus)?.has(methodologyStatus)
|
|
1854
|
+
) {
|
|
1855
|
+
blockers.push({
|
|
1856
|
+
code: "incompatible_lifecycle_state",
|
|
1857
|
+
message: `status ${lifecycleStatus} is incompatible with content_maturity ${methodologyStatus}.`,
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
if (
|
|
1861
|
+
lifecycleStatus === "active" &&
|
|
1862
|
+
!["aligned", "reviewed"].includes(chineseTranslationStatus)
|
|
1863
|
+
) {
|
|
1864
|
+
blockers.push({
|
|
1865
|
+
code: "translation_not_aligned",
|
|
1866
|
+
message: `active PCR requires aligned or reviewed zh-CN translation; found ${chineseTranslationStatus}.`,
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
if (lifecycleStatus === "published" && chineseTranslationStatus !== "reviewed") {
|
|
1870
|
+
blockers.push({
|
|
1871
|
+
code: "translation_not_reviewed",
|
|
1872
|
+
message: `published PCR requires reviewed zh-CN translation; found ${chineseTranslationStatus}.`,
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
if (methodologyStatus === "authored_methodology") {
|
|
1877
|
+
warnings.push({
|
|
1878
|
+
code: "methodology_not_reviewed",
|
|
1879
|
+
message: "Authored methodology is candidate guidance and still requires methodology review.",
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
if (
|
|
1883
|
+
lifecycleStatus === "candidate" &&
|
|
1884
|
+
!["aligned", "reviewed"].includes(chineseTranslationStatus)
|
|
1885
|
+
) {
|
|
1886
|
+
warnings.push({
|
|
1887
|
+
code: "translation_not_aligned",
|
|
1888
|
+
message: `Candidate zh-CN translation is ${chineseTranslationStatus}; use canonical en-US guidance for methodology decisions.`,
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
const uniqueBlockers = deduplicateMessages(blockers);
|
|
1893
|
+
const uniqueWarnings = deduplicateMessages(warnings);
|
|
1894
|
+
const usable = uniqueBlockers.length === 0;
|
|
1895
|
+
const readiness = {
|
|
1896
|
+
status: usable
|
|
1897
|
+
? (REVIEWED_MATURITIES.has(methodologyStatus) && lifecycleStatus !== "candidate" ? "ready" : "review_required")
|
|
1898
|
+
: "unavailable",
|
|
1899
|
+
lifecycle_status: lifecycleStatus,
|
|
1900
|
+
methodology_status: methodologyStatus,
|
|
1901
|
+
structured_projection_available: structuredAvailable,
|
|
1902
|
+
projection_fingerprint: structuredClone(projectionFingerprint),
|
|
1903
|
+
usable_for_guidance: usable,
|
|
1904
|
+
usable_for_validation: usable,
|
|
1905
|
+
blockers: uniqueBlockers,
|
|
1906
|
+
warnings: uniqueWarnings,
|
|
1907
|
+
};
|
|
1908
|
+
assertCoreContract("readiness.schema.json", readiness, {
|
|
1909
|
+
code: "PCR_INTERNAL_CONTRACT_INVALID",
|
|
1910
|
+
entityKind: "PCR readiness",
|
|
1911
|
+
});
|
|
1912
|
+
return readiness;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
function assertPcrUsable({ pcr, operation }) {
|
|
1916
|
+
const usabilityKey = operation === "validation" ? "usable_for_validation" : "usable_for_guidance";
|
|
1917
|
+
if (!pcr.readiness?.[usabilityKey]) {
|
|
1918
|
+
throw new PcrUsabilityError({ pcrId: pcr.id, operation, readiness: pcr.readiness });
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
function buildValidationReport({
|
|
1923
|
+
validationKind,
|
|
1924
|
+
guidance,
|
|
1925
|
+
input,
|
|
1926
|
+
findings,
|
|
1927
|
+
checksPerformed,
|
|
1928
|
+
checksSkipped,
|
|
1929
|
+
}) {
|
|
1930
|
+
const checkedRequirementCount = checksPerformed.reduce((total, check) => total + check.requirement_count, 0);
|
|
1931
|
+
const skippedRequirementCount = checksSkipped.reduce((total, check) => total + check.requirement_count, 0);
|
|
1932
|
+
const completeness = checksPerformed.length === 0
|
|
1933
|
+
? "none"
|
|
1934
|
+
: checksSkipped.length > 0
|
|
1935
|
+
? "partial"
|
|
1936
|
+
: "complete";
|
|
1937
|
+
const findingSummary = countFindingsBySeverity(findings);
|
|
1938
|
+
const validationStatus = findingSummary.error > 0
|
|
1939
|
+
? "failed"
|
|
1940
|
+
: checksPerformed.length === 0
|
|
1941
|
+
? "inconclusive"
|
|
1942
|
+
: "passed";
|
|
1943
|
+
|
|
1944
|
+
const report = {
|
|
1945
|
+
schema_version: 1,
|
|
1946
|
+
validation_kind: validationKind,
|
|
1947
|
+
pcr: guidance.pcr,
|
|
1948
|
+
readiness: structuredClone(guidance.readiness),
|
|
1949
|
+
validation_status: validationStatus,
|
|
1950
|
+
completeness,
|
|
1951
|
+
input,
|
|
1952
|
+
check_coverage: {
|
|
1953
|
+
total_requirement_count: checkedRequirementCount + skippedRequirementCount,
|
|
1954
|
+
checked_requirement_count: checkedRequirementCount,
|
|
1955
|
+
skipped_requirement_count: skippedRequirementCount,
|
|
1956
|
+
checks_performed: checksPerformed,
|
|
1957
|
+
checks_skipped: checksSkipped,
|
|
1958
|
+
},
|
|
1959
|
+
finding_count: findings.length,
|
|
1960
|
+
finding_summary: findingSummary,
|
|
1961
|
+
findings,
|
|
1962
|
+
};
|
|
1963
|
+
assertCoreContract("validation-output.schema.json", report, {
|
|
1964
|
+
code: "PCR_INTERNAL_CONTRACT_INVALID",
|
|
1965
|
+
entityKind: "PCR validation report",
|
|
1966
|
+
});
|
|
1967
|
+
return report;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
function performedCheck(checkId, requirementFamily, requirementCount) {
|
|
1971
|
+
return {
|
|
1972
|
+
check_id: checkId,
|
|
1973
|
+
requirement_family: requirementFamily,
|
|
1974
|
+
requirement_count: requirementCount,
|
|
1975
|
+
evaluated_requirement_count: requirementCount,
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
function addSkippedCheck(checks, checkId, requirementFamily, requirementCount) {
|
|
1980
|
+
if (requirementCount <= 0) {
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
checks.push({
|
|
1984
|
+
check_id: checkId,
|
|
1985
|
+
requirement_family: requirementFamily,
|
|
1986
|
+
requirement_count: requirementCount,
|
|
1987
|
+
reason: "Validator does not yet implement this requirement family.",
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
function countFindingsBySeverity(findings) {
|
|
1992
|
+
const summary = { error: 0, warning: 0, info: 0 };
|
|
1993
|
+
for (const finding of findings) {
|
|
1994
|
+
if (Object.hasOwn(summary, finding.severity)) {
|
|
1995
|
+
summary[finding.severity] += 1;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
return summary;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
function deduplicateMessages(entries) {
|
|
2002
|
+
return [
|
|
2003
|
+
...new Map(
|
|
2004
|
+
entries.map((entry) => [`${entry.code}\0${entry.message}`, entry]),
|
|
2005
|
+
).values(),
|
|
2006
|
+
];
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
function countInventoryRows(processInventory) {
|
|
2010
|
+
let count = 0;
|
|
2011
|
+
for (const process of asArray(processInventory)) {
|
|
2012
|
+
for (const direction of ["inputs", "outputs"]) {
|
|
2013
|
+
const groups = process?.[direction];
|
|
2014
|
+
if (!isRecord(groups)) {
|
|
2015
|
+
continue;
|
|
2016
|
+
}
|
|
2017
|
+
for (const rows of Object.values(groups)) {
|
|
2018
|
+
count += asArray(rows).length;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return count;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
function topLevelRequirementCount(value, excludedKeys = []) {
|
|
2026
|
+
if (!isRecord(value)) {
|
|
2027
|
+
return 0;
|
|
2028
|
+
}
|
|
2029
|
+
const excluded = new Set(excludedKeys);
|
|
2030
|
+
return Object.entries(value).filter(
|
|
2031
|
+
([key, requirement]) => !excluded.has(key) && hasRequirementValue(requirement),
|
|
2032
|
+
).length;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function hasRequirementValue(value) {
|
|
2036
|
+
if (Array.isArray(value)) {
|
|
2037
|
+
return value.some(hasRequirementValue);
|
|
2038
|
+
}
|
|
2039
|
+
if (isRecord(value)) {
|
|
2040
|
+
return Object.values(value).some(hasRequirementValue);
|
|
2041
|
+
}
|
|
2042
|
+
return value !== undefined && value !== null && String(value).trim() !== "";
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
function asArray(value) {
|
|
2046
|
+
return Array.isArray(value) ? value : [];
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
function isRecord(value) {
|
|
2050
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
function findManifestFiles(directory) {
|
|
2054
|
+
const results = [];
|
|
2055
|
+
for (const domainDir of canonicalChildDirectories(directory)) {
|
|
2056
|
+
for (const subdomainDir of canonicalChildDirectories(domainDir)) {
|
|
2057
|
+
for (const pcrDir of canonicalChildDirectories(subdomainDir)) {
|
|
2058
|
+
const manifestPath = path.join(pcrDir, "manifest.yaml");
|
|
2059
|
+
if (isCanonicalFile(manifestPath)) {
|
|
2060
|
+
results.push(manifestPath);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
return results;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
function canonicalChildDirectories(directory) {
|
|
2069
|
+
return readdirSync(directory)
|
|
2070
|
+
.map((entry) => path.join(directory, entry))
|
|
2071
|
+
.filter(isCanonicalDirectory)
|
|
2072
|
+
.sort((left, right) => left.localeCompare(right));
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
function isCanonicalDirectory(directory) {
|
|
2076
|
+
try {
|
|
2077
|
+
const stat = lstatSync(directory);
|
|
2078
|
+
return !stat.isSymbolicLink() && stat.isDirectory();
|
|
2079
|
+
} catch (error) {
|
|
2080
|
+
if (error?.code === "ENOENT") {
|
|
2081
|
+
return false;
|
|
2082
|
+
}
|
|
2083
|
+
throw error;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
function isCanonicalFile(filePath) {
|
|
2088
|
+
try {
|
|
2089
|
+
const stat = lstatSync(filePath);
|
|
2090
|
+
return !stat.isSymbolicLink() && stat.isFile();
|
|
2091
|
+
} catch (error) {
|
|
2092
|
+
if (error?.code === "ENOENT") {
|
|
2093
|
+
return false;
|
|
2094
|
+
}
|
|
2095
|
+
throw error;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
function toPosix(value) {
|
|
2100
|
+
return value.split(path.sep).join("/");
|
|
2101
|
+
}
|