mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 11:56:38 +00:00
204 lines
8.2 KiB
JSON
204 lines
8.2 KiB
JSON
{
|
|
"protocolVersion": "v1",
|
|
"fixtureSet": "inventory",
|
|
"fixture": "field-registry",
|
|
"description": "The frozen v1 inventory snapshot envelope. Every collected member cites its data-collection registry id from protocol/data-collection-fields.json. Any member not listed here is unknown: it is accepted, discarded before validation, never persisted, never echoed back, and its raw text is never logged.",
|
|
"envelope": "InventorySnapshot",
|
|
"schema": "protocol/agent/v1/inventory-snapshot.schema.json",
|
|
"classificationRegistry": "protocol/data-collection-fields.json",
|
|
"level": "L0",
|
|
"source": "inventory",
|
|
"retentionDays": 90,
|
|
"cadence": "per-inventory",
|
|
"supportedMajorVersions": [1],
|
|
"protocolVersionPattern": "^v[1-9][0-9]{0,3}$",
|
|
"unknownFieldPolicy": "accept-and-discard",
|
|
"unknownCoarseFlagPolicy": "discard",
|
|
"unknownMajorVersionPolicy": "reject",
|
|
"fields": [
|
|
{
|
|
"name": "protocolVersion",
|
|
"registryId": null,
|
|
"collected": false,
|
|
"requiredness": "required",
|
|
"type": "string",
|
|
"default": null,
|
|
"limits": {
|
|
"type": "string",
|
|
"pattern": "^v[1-9][0-9]{0,3}$"
|
|
},
|
|
"includedInContentHash": false,
|
|
"note": "Negotiation input shared with the frozen v1 negotiation envelope. It gates processing, is never persisted as an inventory attribute, and therefore has no data-collection registry id."
|
|
},
|
|
{
|
|
"name": "rustfsVersion",
|
|
"registryId": "inventory.rustfsVersion",
|
|
"collected": true,
|
|
"requiredness": "required",
|
|
"type": "string",
|
|
"default": null,
|
|
"limits": {
|
|
"type": "string",
|
|
"pattern": "^(0|[1-9][0-9]{0,3})\\.(0|[1-9][0-9]{0,3})\\.(0|[1-9][0-9]{0,3})$"
|
|
},
|
|
"includedInContentHash": true,
|
|
"note": "Coarse release only. Pre-release and build metadata are rejected because a build identifier fingerprints a private build."
|
|
},
|
|
{
|
|
"name": "osVersion",
|
|
"registryId": "inventory.osVersion",
|
|
"collected": true,
|
|
"requiredness": "optional",
|
|
"type": ["object", "null"],
|
|
"default": null,
|
|
"limits": {
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"required": ["family", "major", "minor"]
|
|
},
|
|
"memberLimits": {
|
|
"family": {
|
|
"type": "string",
|
|
"enum": ["linux", "darwin", "windows", "freebsd", "other"]
|
|
},
|
|
"major": { "type": "integer", "minimum": 0, "maximum": 9999 },
|
|
"minor": { "type": "integer", "minimum": 0, "maximum": 9999 }
|
|
},
|
|
"includedInContentHash": true,
|
|
"note": "The family is a closed vocabulary, never free text. An unlisted operating system reports 'other'. Unknown members of this object are discarded exactly like unknown top-level members."
|
|
},
|
|
{
|
|
"name": "nodeCount",
|
|
"registryId": "inventory.nodeCount",
|
|
"collected": true,
|
|
"requiredness": "required",
|
|
"type": "integer",
|
|
"default": null,
|
|
"limits": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"maximum": 4096
|
|
},
|
|
"includedInContentHash": true,
|
|
"note": "A cluster reporting inventory has at least one node."
|
|
},
|
|
{
|
|
"name": "driveCount",
|
|
"registryId": "inventory.driveCount",
|
|
"collected": true,
|
|
"requiredness": "required",
|
|
"type": "integer",
|
|
"default": null,
|
|
"limits": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"maximum": 1048576
|
|
},
|
|
"includedInContentHash": true,
|
|
"note": "Zero is legal while a cluster is being provisioned."
|
|
},
|
|
{
|
|
"name": "capacityTotalBytes",
|
|
"registryId": "inventory.capacityTotalBytes",
|
|
"collected": true,
|
|
"requiredness": "required",
|
|
"type": "integer",
|
|
"default": null,
|
|
"limits": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"maximum": 9007199254740991
|
|
},
|
|
"includedInContentHash": true,
|
|
"note": "2^53-1 is the largest integer Rust u64, PHP int, JavaScript number, and JSON all represent exactly. Above the cap is a validation error, never a silent truncation."
|
|
},
|
|
{
|
|
"name": "capacityUsedBytes",
|
|
"registryId": "inventory.capacityUsedBytes",
|
|
"collected": true,
|
|
"requiredness": "required",
|
|
"type": "integer",
|
|
"default": null,
|
|
"limits": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"maximum": 9007199254740991
|
|
},
|
|
"includedInContentHash": true,
|
|
"note": "Must not exceed capacityTotalBytes. JSON Schema cannot compare two members, so the validator enforces that invariant separately."
|
|
},
|
|
{
|
|
"name": "coarseFlags",
|
|
"registryId": "inventory.coarseFlags",
|
|
"collected": true,
|
|
"requiredness": "optional",
|
|
"type": "array",
|
|
"default": [],
|
|
"limits": {
|
|
"type": "array",
|
|
"maxItems": 8,
|
|
"uniqueItems": true
|
|
},
|
|
"itemLimits": {
|
|
"type": "string",
|
|
"enum": [
|
|
"capacity.critical",
|
|
"capacity.warning",
|
|
"clock.skew",
|
|
"cluster.degraded",
|
|
"cluster.healing",
|
|
"cluster.readonly",
|
|
"drive.offline",
|
|
"node.offline"
|
|
]
|
|
},
|
|
"includedInContentHash": true,
|
|
"note": "Allow-listed conditions only. Normalization discards unknown tokens, de-duplicates, and sorts ascending by UTF-8 bytes, so agent-side ordering never changes the content hash."
|
|
}
|
|
],
|
|
"errorReasons": {
|
|
"description": "Inventory-scoped ErrorInfo reasons. The negotiation reason is not redeclared here: an unsupported major version fails with UNSUPPORTED_PROTOCOL from protocol/agent/v1/fixtures/auth/error-codes.json, before any inventory member is read. INVENTORY_FIELD_INVALID is new in this issue and must not collide with a reason already frozen for authentication.",
|
|
"versionFailure": "UNSUPPORTED_PROTOCOL",
|
|
"payloadFailure": {
|
|
"reason": "INVENTORY_FIELD_INVALID",
|
|
"status": "INVALID_ARGUMENT",
|
|
"httpStatus": 400,
|
|
"domain": "rustfs.connect",
|
|
"meaning": "The normalized inventory snapshot violates the frozen schema or a cross-field invariant."
|
|
},
|
|
"disclosureRules": [
|
|
"A rejection names the frozen member path that failed and never the value that failed.",
|
|
"A rejection never names, quotes, or counts an unknown member, so discarded text cannot reach an error body or a log line.",
|
|
"A raw input bound rejection names the bound that was exceeded and never the body."
|
|
]
|
|
},
|
|
"rawInputBounds": {
|
|
"description": "Denial-of-service bounds applied to the raw body after the version gate and before normalization. They are deliberately far above what a v1 snapshot needs so that additive growth never trips them. A violation is rejected without naming or echoing the offending value.",
|
|
"maxBodyBytes": 8192,
|
|
"maxDepth": 8,
|
|
"maxTopLevelMembers": 64,
|
|
"maxRawCoarseFlagItems": 64,
|
|
"reason": "INVENTORY_FIELD_INVALID",
|
|
"httpStatus": 400
|
|
},
|
|
"crossFieldInvariants": [
|
|
{
|
|
"name": "usedNeverExceedsTotal",
|
|
"expression": "capacityUsedBytes <= capacityTotalBytes",
|
|
"reason": "INVENTORY_FIELD_INVALID"
|
|
}
|
|
],
|
|
"forbiddenFieldClasses": {
|
|
"description": "L0 inventory cannot express any of these. The frozen schema sets additionalProperties false at every level and every string member is an enum or a numeric-component pattern, so none of these sample values can be carried by any member, known or unknown, that survives normalization.",
|
|
"classes": [
|
|
{ "class": "bucket", "samples": ["customer-backups", "prod-media-eu"] },
|
|
{ "class": "object", "samples": ["invoices/2026/08/inv-1.pdf", "db-dump.sql.gz"] },
|
|
{ "class": "path", "samples": ["/var/lib/rustfs/data/disk1", "C:\\rustfs\\data"] },
|
|
{ "class": "endpoint", "samples": ["https://rustfs.internal:9000", "10.4.2.17:9000", "node-3.storage.example.com"] },
|
|
{ "class": "configuration", "samples": ["RUSTFS_ERASURE_SET_SIZE=12", "{\"tls\":{\"minVersion\":\"1.3\"}}"] },
|
|
{ "class": "credential", "samples": ["AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "Bearer eyJhbGciOiJIUzI1NiJ9.e30.c2ln", "-----BEGIN PRIVATE KEY-----"] },
|
|
{ "class": "identifier", "samples": ["node-3.storage.example.com", "aa:bb:cc:dd:ee:ff", "acme-corp"] }
|
|
]
|
|
}
|
|
}
|