{
  "protocolVersion": "v1",
  "fixtureSet": "redaction",
  "fixture": "ruleset",
  "description": "The frozen deterministic redaction contract. A support bundle manifest stores redactionVersion and rulesetHash so a reader can prove which rules produced a redacted document, and a RustFS agent reproduces the same decisions from canonicalForm alone.",
  "redactionVersion": "rustfs.connect.redaction.v1",
  "redactionVersionFormat": "^rustfs\\.connect\\.redaction\\.v[1-9][0-9]*$",
  "redactionVersionNotes": [
    "An opaque stable identifier, not a semantic version: compare it for equality, never order it.",
    "A change to any line of canonicalForm changes rulesetHash and requires a new major."
  ],
  "rulesetHash": "b37436d8e72515394a122d633865b1dc028d4ece349352a0a3a23f52ca4285f3",
  "rulesetHashAlgorithm": "sha256",
  "rulesetHashInput": "The canonicalForm lines below joined with U+000A and terminated with a final U+000A, encoded as UTF-8.",
  "collectionDecision": {
    "registry": "protocol/data-collection-fields.json",
    "rule": "Stage one is an allow-list. A field id absent from the registry is removed before its value is read, so an unknown, newly invented, or L2/L3 field can never be collected no matter what it contains. The value rules below are stage two and never grant collection."
  },
  "placeholder": {
    "token": "[REDACTED]",
    "rule": "One constant token for every redaction, carrying no rule name, no offset, no length, and no digest of the removed value. Redaction is not reversible and the result records no hash of anything it removed."
  },
  "output": {
    "canonicalJson": "Object keys are emitted in ascending byte order and an object that loses every entry is removed from its parent, so the same input and version always produce the same bytes.",
    "counts": [
      "droppedField: a field the allow-list refused, a key that is not a plain ASCII identifier, or an object left with no entries.",
      "redactedValue: a value replaced by the placeholder.",
      "redactedOversizeValue: a value replaced because it is longer than maxValueBytes and cannot be scanned within budget."
    ]
  },
  "coverage": {
    "AWS_ACCESS_KEY_ID": "S3 and AWS access key ids.",
    "AWS_SECRET_ACCESS_KEY": "A standalone 40-character S3 secret access key. Mixed case plus a digit is required so a single-case hex digest of the same length is not redacted.",
    "BEARER_TOKEN": "HTTP bearer credentials, including a captured Authorization header.",
    "CREDENTIAL_ASSIGNMENT": "API keys, registration tokens, session tokens, passphrases, and KMS secrets written as an assignment in a connection string, environment dump, or configuration snippet.",
    "JWT": "JSON web tokens presented on their own.",
    "PASSWORD_ASSIGNMENT": "A password written as an assignment, including inside a DSN.",
    "PEM_PRIVATE_KEY": "Any PEM private key or private key block header, which covers device keys and KMS private material.",
    "SESSION_ID_ASSIGNMENT": "Session and CSRF identifiers written as an assignment.",
    "URL_CREDENTIALS": "Credentials in a URL or DSN authority. The whole value is replaced, so the host the credential belonged to is not published either."
  },
  "keyRuleNormalisation": "ASCII-lowercase the key and remove '_', '-', and '.', so secret_access_key, Secret-Access-Key, and secretAccessKey are the same key. A key that is not a plain ASCII identifier is dropped rather than normalised.",
  "canonicalForm": [
    "version\trustfs.connect.redaction.v1",
    "placeholder\t[REDACTED]",
    "budget\tmaxInputBytes\t262144",
    "budget\tmaxDepth\t8",
    "budget\tmaxNodes\t4096",
    "budget\tmaxValueBytes\t4096",
    "keyPattern\t/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/D",
    "field\theartbeat.agentVersion\tL0",
    "field\theartbeat.capabilities\tL0",
    "field\theartbeat.clientTime\tL0",
    "field\theartbeat.coarseNodeSummary\tL0",
    "field\theartbeat.protocolVersion\tL0",
    "field\theartbeat.sequence\tL0",
    "field\tinventory.capacityTotalBytes\tL0",
    "field\tinventory.capacityUsedBytes\tL0",
    "field\tinventory.coarseFlags\tL0",
    "field\tinventory.driveCount\tL0",
    "field\tinventory.nodeCount\tL0",
    "field\tinventory.osVersion\tL0",
    "field\tinventory.rustfsVersion\tL0",
    "field\toffline.capacityTotalBytes\tL0",
    "field\toffline.capacityUsedBytes\tL0",
    "field\toffline.coarseHealthFlags\tL0",
    "field\toffline.cpuSummary\tL1",
    "field\toffline.driveCount\tL0",
    "field\toffline.filesystemSummary\tL1",
    "field\toffline.kernelSummary\tL1",
    "field\toffline.memorySummary\tL1",
    "field\toffline.networkSummary\tL1",
    "field\toffline.nodeCount\tL0",
    "field\toffline.osSummary\tL1",
    "field\toffline.rustfsVersion\tL0",
    "keyRule\taccesskey",
    "keyRule\taccesskeyid",
    "keyRule\tapikey",
    "keyRule\tapitoken",
    "keyRule\tauthorization",
    "keyRule\tbearertoken",
    "keyRule\tcookie",
    "keyRule\tcredential",
    "keyRule\tcredentials",
    "keyRule\tcsrftoken",
    "keyRule\tkmskey",
    "keyRule\tkmskeyid",
    "keyRule\tkmsmasterkey",
    "keyRule\tkmssecret",
    "keyRule\tpassphrase",
    "keyRule\tpasswd",
    "keyRule\tpassword",
    "keyRule\tprivatekey",
    "keyRule\tpwd",
    "keyRule\trefreshtoken",
    "keyRule\tregistrationtoken",
    "keyRule\tsecret",
    "keyRule\tsecretaccesskey",
    "keyRule\tsecretkey",
    "keyRule\tsessioncookie",
    "keyRule\tsessionid",
    "keyRule\tsessiontoken",
    "keyRule\tsigningkey",
    "keyRule\ttoken",
    "keyRule\txsrftoken",
    "valueRule\tAWS_ACCESS_KEY_ID\t/\\b(?:A3T[A-Z0-9]{2}|ABIA|ACCA|AKIA|ASIA)[A-Z0-9]{16}\\b/",
    "valueRule\tAWS_SECRET_ACCESS_KEY\t/(?<![A-Za-z0-9+\\/])(?=[A-Za-z0-9+\\/]{0,39}[a-z])(?=[A-Za-z0-9+\\/]{0,39}[A-Z])(?=[A-Za-z0-9+\\/]{0,39}[0-9])[A-Za-z0-9+\\/]{40}(?![A-Za-z0-9+\\/=])/",
    "valueRule\tBEARER_TOKEN\t/(?i)\\bbearer\\s{1,8}[A-Za-z0-9\\-._~+\\/]{8,4096}={0,2}/",
    "valueRule\tCREDENTIAL_ASSIGNMENT\t/(?i)\\b[a-z0-9_.-]{0,24}(?:access[_.-]?key(?:[_.-]?id)?|api[_.-]?key|credentials?|passphrase|secret(?:[_.-]?key)?|token)\\b\\s{0,8}[:=]\\s{0,8}[\"\\x27]?[A-Za-z0-9\\-._~+\\/=]{8,4096}/",
    "valueRule\tJWT\t/\\beyJ[A-Za-z0-9_-]{4,4096}\\.[A-Za-z0-9_-]{4,4096}\\.[A-Za-z0-9_-]{4,4096}/",
    "valueRule\tPASSWORD_ASSIGNMENT\t/(?i)\\b(?:passwd|password|pwd)\\b\\s{0,8}[:=]\\s{0,8}\\S/",
    "valueRule\tPEM_PRIVATE_KEY\t/-----BEGIN [A-Z0-9 ]{0,32}PRIVATE KEY(?: BLOCK)?-----/",
    "valueRule\tSESSION_ID_ASSIGNMENT\t/(?i)\\b(?:csrf[_.-]?token|jsessionid|phpsessid|sess|session|sid|xsrf[_.-]?token)(?:[_.-]?id)?\\b\\s{0,8}[:=]\\s{0,8}[\"\\x27]?[A-Za-z0-9%\\-._~+\\/]{12,4096}/",
    "valueRule\tURL_CREDENTIALS\t/\\b[a-zA-Z][a-zA-Z0-9+.\\-]{0,31}:\\/\\/[^\\s\\/@:]{1,256}(?::[^\\s\\/@]{0,256})?@/"
  ]
}
