Skip to content

Commit 0749d3d

Browse files
committed
Flip duly_duty.source default from catalog to self (#50)
A hand-created duty was born into the governed, scoreable set because the source select's default option was 'catalog'. Every path that legitimately produces a governed duty already stamps source explicitly (duly_catalog_apply writes 'catalog' per #34; the assignment fan-out writes 'assigned' on duly_task per #33), so the field default was only ever reached by a hand-created duty, which is by definition self-declared. Moves default: true from the catalog option to the self option on duly_duty.source. Adds a test pinning the direction next to the existing invariant tests: the default is self, and both governed values (catalog, assigned) are reachable only by explicit assignment, never as a fallback.
1 parent 15c10d8 commit 0749d3d

2 files changed

Lines changed: 29 additions & 2 deletions

File tree

‎src/objects/duty.object.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,16 @@ export const Duty = ObjectSchema.create({
8383
label: 'Source',
8484
required: true,
8585
options: [
86-
{ label: 'Role catalog', value: 'catalog', color: '#16515F', default: true },
86+
{ label: 'Role catalog', value: 'catalog', color: '#16515F' },
8787
{ label: 'Assigned by manager', value: 'assigned', color: '#8C6512' },
88-
{ label: 'Self-declared', value: 'self', color: '#576B73' },
88+
// The default. Every path that legitimately produces a governed duty
89+
// stamps `source` explicitly — `duly_catalog_apply` writes 'catalog'
90+
// (#34), the assignment fan-out writes 'assigned' (#33) — so the
91+
// default is only ever reached by a hand-created duty, which is by
92+
// definition self-declared. Fail-safe direction: a producer that
93+
// forgets to stamp caliber produces an unscored duty, not a scored
94+
// one (#50).
95+
{ label: 'Self-declared', value: 'self', color: '#576B73', default: true },
8996
],
9097
}),
9198

‎test/invariants.test.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,26 @@ describe('product invariants', () => {
5959
}
6060
});
6161

62+
it('a hand-created duty is self-declared, not born governed (#50)', () => {
63+
// The safe default for an ambiguous duty is the UNscoreable one. A duty
64+
// created with no `source` supplied must land as 'self' — never
65+
// 'catalog', which would additionally expose it to duly_catalog_sync's
66+
// cadence rewrite for a catalog item it never came from.
67+
const options = Duty.fields.source.options ?? [];
68+
const defaults = options.filter((o) => o.default);
69+
expect(defaults, 'exactly one option may claim the default').toHaveLength(1);
70+
expect(defaults[0]?.value).toBe('self');
71+
72+
// The two governed calibers are reachable only when the producer that
73+
// knows states them explicitly — duly_catalog_apply writes 'catalog'
74+
// (#34), the assignment fan-out writes 'assigned' (#33). Neither may ride
75+
// in on the field default.
76+
for (const governed of ['catalog', 'assigned'] as const) {
77+
const option = options.find((o) => o.value === governed);
78+
expect(option?.default, `${governed} must not be the default`).not.toBe(true);
79+
}
80+
});
81+
6282
it('every object states its sharing model explicitly', () => {
6383
for (const schema of [Duty, Task, LogEntry, CatalogItem]) {
6484
expect(schema.sharingModel, `${schema.name} must state an OWD`).toBeTruthy();

0 commit comments

Comments
 (0)