contract: pin the canonical check-result shape across all outcome classes
A new rigor contract test (public-contract) asserts every check result —
allowed, denied, missing_node, no_config, cycle, threshold, binary — carries
possibility, reliability, validity, and reason with defined invariants
(possibility/reliability in [0,1], validity labels in the taxonomy, denied
decisions leak zero reliability, JSON round-trip stable).
The pin surfaced three real shape gaps in AuthorizationChecker, now fixed:
the no_relation fast-path miss and binary direct-miss emitted no validity,
and the binary direct-allow path omitted reliability. All structural and
binary return sites now emit the canonical {possibility, reason, reliability,
validity} contract.
Also pins isReachable's boolean|null deferral contract, explain enrichment,
snapshot round-trip outcome preservation, and read-idempotent node queries.
This commit is contained in:
@@ -108,6 +108,8 @@ export class AuthorizationChecker {
|
||||
if (userId === undefined || objectId === undefined) {
|
||||
const result = {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
...(includeMeta && { meta: { reason: 'missing_node' } }),
|
||||
reason: 'missing_node'
|
||||
};
|
||||
@@ -128,6 +130,8 @@ export class AuthorizationChecker {
|
||||
if (_visited.has(visitKey)) {
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
...(includeMeta && { meta: { reason: 'cycle' } }),
|
||||
reason: 'cycle'
|
||||
};
|
||||
@@ -137,6 +141,8 @@ export class AuthorizationChecker {
|
||||
if (visited.userKey === userKey && visited.relation === relation && visited.objectKey === objectKey) {
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
...(includeMeta && { meta: { reason: 'cycle' } }),
|
||||
reason: 'cycle'
|
||||
};
|
||||
@@ -217,6 +223,7 @@ export class AuthorizationChecker {
|
||||
result = {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
...(includeMeta && { meta: { reason: 'no_relation' } }),
|
||||
reason: 'no_relation',
|
||||
...(remediation ? { remediation } : {})
|
||||
@@ -247,6 +254,8 @@ export class AuthorizationChecker {
|
||||
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
...(includeMeta && {
|
||||
meta: {
|
||||
reason: 'missing_node',
|
||||
@@ -261,6 +270,8 @@ export class AuthorizationChecker {
|
||||
if (!config) {
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
...(includeMeta && { meta: { reason: 'no_config' } }),
|
||||
reason: 'no_config'
|
||||
};
|
||||
@@ -285,6 +296,8 @@ export class AuthorizationChecker {
|
||||
if (_visited.has(visitKey)) {
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
...(includeMeta && { meta: { reason: 'cycle' } }),
|
||||
reason: 'cycle'
|
||||
};
|
||||
@@ -294,6 +307,8 @@ export class AuthorizationChecker {
|
||||
if (visited.userKey === userKey && visited.relation === relation && visited.objectKey === objectKey) {
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
...(includeMeta && { meta: { reason: 'cycle' } }),
|
||||
reason: 'cycle'
|
||||
};
|
||||
@@ -636,6 +651,8 @@ export class AuthorizationChecker {
|
||||
if (userId === undefined || objectId === undefined) {
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
reason: 'missing_node',
|
||||
binary: true,
|
||||
...(evaluation && { evaluation })
|
||||
@@ -649,6 +666,7 @@ export class AuthorizationChecker {
|
||||
if (_visited.has(visitKey)) {
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
reason: 'cycle',
|
||||
binary: true,
|
||||
@@ -660,6 +678,7 @@ export class AuthorizationChecker {
|
||||
if (visited.userKey === userKey && visited.relation === relation && visited.objectKey === objectKey) {
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
reason: 'cycle',
|
||||
binary: true,
|
||||
@@ -675,6 +694,8 @@ export class AuthorizationChecker {
|
||||
if (!config) {
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
reason: 'no_config',
|
||||
binary: true,
|
||||
...(evaluation && { evaluation })
|
||||
@@ -709,6 +730,9 @@ export class AuthorizationChecker {
|
||||
|
||||
return {
|
||||
possibility: directRel.possibility,
|
||||
reliability: directRel.possibility > 0
|
||||
? (directRel.reliability !== undefined ? directRel.reliability : 1.0)
|
||||
: 0,
|
||||
validity: includeMeta
|
||||
? (directRel.validity !== undefined
|
||||
? buildValidity('identity', [effectiveRelation], [normalizeValidity(directRel.validity)], 1, directRel.possibility)
|
||||
@@ -731,6 +755,7 @@ export class AuthorizationChecker {
|
||||
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
reason: 'insufficient_confidence',
|
||||
binary: true,
|
||||
@@ -849,6 +874,8 @@ export class AuthorizationChecker {
|
||||
|
||||
return {
|
||||
possibility: 0,
|
||||
reliability: 0,
|
||||
validity: includeMeta ? DEFAULT_VALIDITY : minimalValidity(DEFAULT_VALIDITY),
|
||||
reason: 'deny',
|
||||
binary: true,
|
||||
...(evaluation && { evaluation }),
|
||||
|
||||
Reference in New Issue
Block a user