Files
rustfs/protocol/agent/v1/fixtures/inventory/field-registry.json
T
overtrue 3b5164032a feat(connect): add device identity store and registration proof
A RustFS cluster device needs a durable identity before it can exchange a
one-time registration token for a certificate. This adds the device-side
half of that exchange, which rustfs/connect already verifies.

`connect::identity` builds the canonical registration transcript frozen by
protocol/agent/v1/registration-proof.md, signs it as low-S ES256, and emits
the PKCS#10 certificate request Connect consumes for its SubjectPublicKeyInfo.
`connect::identity_store` seals the P-256 key at mode 0600 and publishes it
through a no-clobber link, so a retry or a concurrent start returns the
original identity rather than minting a second one, and a corrupt or widened
key is refused rather than silently replaced.

The protocol fixture set is copied here byte-identically because
fixture-sets.json names this repository as the consumer copy; the tests
verify it against its own manifests and cross-verify Connect-produced ECDSA
proofs against transcripts rebuilt locally.

Nothing starts a task or touches the S3 data path: an unenrolled deployment
generates no key and holds no identity.
2026-08-19 12:54:50 +08:00

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"] }
]
}
}