A shared library that nobody finds is not reuse. This chapter turns reuse first into a layered control: a governed asset catalog, an instructions file that sends the coding agent to it, an advisory agent hook, a recorded decision, and deterministic checks in the CI pipeline (continuous integration, the automated build and test run on every change) that enforce the result. Counts in this chapter are illustrative.
Local agent hooks are advisory. They nudge the coding agent and they can be skipped. Enforcement lives in the CI pipeline and in branch protection, which run whether or not anyone used an agent.
1. The problem · ten implementations of one idea
The organization had ten different date-and-time implementations (illustrative) across its services. A shared library, shared-datetime, already existed. Each implementation got the domain wrong in its own way, and three of them turned a contractual due date into a moment in time.
| # | Where it lives | How it handles the date | What went wrong |
|---|---|---|---|
| 1 | Web app | new Date(dueDate) on a date-only string | The string becomes midnight UTC (Coordinated Universal Time); every customer west of UTC, the West coast included, sees the day before |
| 2 | notification-service | Stores the due date as a timestamp at 00:00 Eastern | Reminders sent to Pacific customers name the wrong day |
| 3 | statement-service | Converts with a fixed -05:00 offset | Wrong day for Pacific customers; ignores DST (daylight saving time) half the year |
| 4 | Mobile app | Keeps the date correct; shows the cutoff in the device zone | A travelling customer sees a cutoff with no zone label |
| 5 | Marketing-email job | Formats the cutoff with the server's default zone | Customers outside the data-center zone see the wrong cutoff time |
| 6 | Contact-center desktop | A copied, pinned old version of shared-datetime | Misses a later DST rule fix |
| 7 | Autopay job | Its own business-day calculator | Holiday list hard-coded for one year |
| 8 | Collections batch | Days past due from the server clock date | Ignores the payment calendar |
| 9 | Reporting job | Truncates payment instants to a date in UTC | Late-evening Pacific payments land on the next day |
| 10 | Dispute service | A hand-written date parser | Accepts invalid dates such as February 30 |
| Why it happened | What it means |
|---|---|
| Not discoverable | The library had no catalog entry, so search found nothing and the developer wrote a helper |
| No contract | The library offered types but nobody said which type a due date must be |
| Agents accelerate it | A coding agent writes a fresh helper in seconds; without context it will not look for the library |
| No enforcement | Nothing in the pipeline failed when a date-only field was parsed as an instant |
The fix is not "use the library". The fix is a temporal-domain contract, owned by architecture and published in the catalog, that says what each kind of time value is.
2. The temporal-domain contract
The contract names four kinds of time value and one rule for each. The worked example in chapter 8 applies it end to end.
| Concept | What it is | Type in shared-datetime | Authoritative source | Rule | Example |
|---|---|---|---|---|---|
| Due date | A date-only value: a calendar date in the contract | LocalDate | payment-schedule-service | Never converted, never given a time or zone | 2026-11-16 everywhere, on every channel |
| Cutoff | An instant: one moment, the same worldwide | Instant | payment-schedule-service computes it (it owns the contractual business-calendar rule: 5:00 PM Eastern on the due date, illustrative fictional) and exposes it as an instant | Stored and passed as an instant in UTC; shared-datetime only formats it (formatPaymentCutoff), never computes it | 2026-11-16T22:00:00Z |
| Customer zone | An IANA time zone (Internet Assigned Numbers Authority zone name) | ZoneId | profile-service | A zone name, never a fixed offset; fallback labelled | America/Los_Angeles |
| Display | Text for a person | formatInstant and formatDate | Each channel, through the library | Format the instant in the customer zone; print the date as is | "Due Nov 16 · pay by 2:00 PM PT" (PT = Pacific time) |
| Fallback zone | The zone used when the profile has none | ZoneId from a table | profile-service policy table (product owns) | Account's state of residence; the label says so | "2:00 PM PT (based on your state of residence)" |
| Issued statement | A record | Snapshot text | statement-service | Immutable once issued; later zone changes apply to the next statement | October statement keeps its printed cutoff |
| Never do this | Why |
|---|---|
| Parse a date-only field into a date-time | It invents a midnight and a zone that the contract never had |
Store an offset such as -05:00 instead of a zone name | Offsets change with DST; zone names carry the rules |
| Format with the server or device default zone | The customer's zone is data, not an environment setting |
| Re-render an issued statement | A statement is a record under records management |
3. The reference architecture · eight layers
Each layer catches something the others miss. No single layer proves that a duplicate does not exist.
| Layer | Who or what acts | Catches | Does not catch | Enforcement point |
|---|---|---|---|---|
| 1 · Asset catalog | Architecture owns it; asset owners keep entries current | Lets anyone find shared-datetime, its version, status and limits | An asset nobody registered; a stale entry | None; it is a source of truth |
| 2 · Instructions file | The coding agent reads it at session start | Tells the agent to query the catalog and which types to use | An agent session that ignores or truncates it | None; it is context |
| 3 · Advisory check | An agent hook or an MCP (Model Context Protocol) catalog tool, at plan time or before a new utility file is written | Known patterns: a new file named like a date helper, new Date( on a due date | Duplicates with new names; work done without an agent | Local, advisory, can be bypassed |
| 4 · Decision record | The developer records reuse, extend or create, with the reason | Makes the choice visible and reviewable | A wrong reason that a reviewer accepts | Reviewed in the pull request |
| 5 · CI checks | The CI pipeline, on every pull request | Banned patterns, dependency policy, architecture rules, SBOM (software bill of materials) and licence, tests, decision record present | Semantic duplicates that match no rule | Blocking: the build fails |
| 6 · Branch protection | The repository platform; required reviewers | Merging without passing checks or without an independent reviewer | A reviewer who approves without reading | Blocking: merge refused |
| 7 · Exceptions | The exception owner, with an expiry | A justified new asset or a temporary bypass, kept visible | An exception renewed forever | Exception register; the CI check reads it |
| 8 · Catalog feedback | Architecture and asset owners, monthly | False positives, missing assets, adoption, deprecation | Nothing on its own; it improves layers 1 to 5 | None; it feeds the others |
4. Layer 1 · the catalog entry
A catalog entry is evidence, not marketing. The coding agent and the developer both read it, so it states limits as clearly as features.
| Field | shared-datetime |
|---|---|
| Capability | Date-only values, instants, zone-aware formatting, business calendar |
| Owner | Architecture, temporal working group; on-call rota published |
| Versions | 4.2 current; 3.x deprecated, end of support in two quarters (illustrative) |
| Status | Supported |
| Assurance level | Reviewed by security; mutation-tested |
| Consumers | 7 teams / 31 repositories (illustrative) |
| Limitations | No labelled cutoff formatter yet (formatPaymentCutoff proposed, display only); never computes a cutoff; zone data updated monthly; no historical calendars before 2015 |
| Examples | Due date display, cutoff display, statement snapshot, reminder text |
| Contract | Temporal-domain contract v1, linked |
5. Layer 2 · the instructions file
The instructions file is the team's standing context for the coding agent, reviewed like code. This excerpt is illustrative and vendor-neutral.
## Dates and times (temporal-domain contract v1)
- Before writing any date, time, zone or calendar helper, query the asset catalog
for the capability "temporal". Prefer reuse; extend if close; create only with a
decision record.
- Use shared-datetime 4.x. Never add another date library.
- A due date is a date-only value: LocalDate. Never parse it into a date-time.
- A cutoff is an Instant computed only by payment-schedule-service. Never compute it
from the due date. Format it with formatPaymentCutoff(cutoffInstant, customerZone).
- The customer zone comes from profile-service as an IANA zone name. If missing,
use the fallback table and show the fallback label.
- Issued statements are immutable. Do not re-render them.
- Record every reuse, extend or create choice in reuse-decision.yaml in the pull request.
6. Layer 3 · the advisory hook
An agent hook is a small program that the agent's runtime calls at a fixed event, such as just before the agent writes a file. This one fires when the agent is about to create something that looks like a new date utility. It returns candidates from the catalog with evidence; it never claims that reuse is certain.
| Spec field | "New date utility" advisory hook |
|---|---|
| Event | Before a file-write or file-create tool call by the coding agent |
| Scope | Repositories that consume customer-facing dates; paths under source folders; not tests or generated code |
| Input | Target path, proposed file content, repository name; no customer data |
| Trusted source | The asset catalog API (application programming interface), read-only, versioned |
| Output schema | decision (allow, advise), candidates (asset, version, capability, status, evidence), ask (record the choice) |
| Timeout | 3 seconds (illustrative) |
| Fail-open or closed | Fail-open with a warning: it is advisory, so a slow catalog never blocks work |
| Bypass authority | The developer may proceed; the decision record and CI still apply |
| False-positive handling | The developer marks it "not temporal" in one line; architecture reviews the marks monthly |
| Audit event | reuse.advisory.shown and reuse.advisory.dismissed, with repository, path and candidate |
| Enforcement point | None here. Enforcement is layer 5 and layer 6 |
A generic configuration sketch (not any product's syntax):
hooks:
- name: reuse-advisory-new-date-utility
event: before_tool_call # agent is about to write or create a file
match:
tools: [write_file, create_file]
paths: ["src/**"]
exclude: ["**/test/**", "**/generated/**"]
content_regex: "new Date\\(|Date\\.parse\\(|setHours\\(|[+-]0[0-9]:00"
run: catalog-check --capability temporal --stdin
timeout_ms: 3000
on_timeout: allow_with_warning # fail-open: advisory only
on_match: inject_context # candidates go back to the agent
audit_event: reuse.advisory.shown
What the hook returns to the agent:
{
"decision": "advise",
"candidates": [
{
"asset": "shared-datetime",
"version": "4.2",
"capability": "LocalDate due date; formatInstant for the source cutoff instant in customer zone",
"status": "supported",
"evidence": "catalog entry; temporal-domain contract v1; 7 teams / 31 repositories (illustrative)"
}
],
"ask": "Record reuse, extend or create in reuse-decision.yaml"
}
In Claude Code this is a PreToolUse hook in settings (Claude Code hooks); in GitHub Copilot CLI a preToolUse hook (GitHub Copilot hooks). Verify current docs before you build one; event names and payloads change.
| The hook catches | The hook does not catch |
|---|---|
| A new file named like a date helper | A helper hidden inside a large file with an unrelated name |
new Date( or a fixed offset in proposed code | Code written in an editor without the agent |
| An agent that skipped the instructions file | An agent run where hooks are disabled locally |
| A missing decision record, by asking for one | A decision record with a poor reason |
7. Layer 4 · the reuse decision record
The reuse decision record is a small machine-readable file in the pull request. The CI pipeline checks that it exists; the reviewer checks that its reason holds.
# reuse-decision.yaml
change: show contractual due date and payment cutoff consistently
capability: temporal
decision: extend # reuse | extend | create
asset: shared-datetime
version: "4.2"
reuse:
- LocalDate for the due date (no conversion)
- formatInstant(cutoff, customerZone) for the cutoff
extend:
what: formatPaymentCutoff(cutoffInstant, customerZone), display only: formats the
authoritative instant from payment-schedule-service with the zone label
(or the labelled fallback zone); never calculates or overrides the cutoff
why: three services recomputed the cutoff differently; payment-schedule-service alone
computes it, and 4.2 has no labelled cutoff formatter
owner_review: architecture temporal working group
create: none
alternatives_rejected:
- local helper in notification-service (duplicates the rule)
contract: temporal-domain-contract v1
decided_by: developer
reviewed_by: architect # not the author
date: 2026-10-29
8. Layers 5 and 6 · CI checks and branch protection
These layers fail closed. They run on every pull request, from any tool or none.
| Check | Fails the build when | Owner |
|---|---|---|
| Banned pattern | A date-only field is parsed as an instant, or a fixed offset appears | Architecture |
| Dependency policy | A second date library is added | Architecture and security |
| Architecture rule | A channel or library computes a cutoff from the due date instead of displaying the payment-schedule-service instant with formatPaymentCutoff | Architecture |
| SBOM and licence | A new dependency has no SBOM entry or a licence outside policy | Security and legal |
| Tests | The temporal test matrix fails | The team |
| Decision record | A new file under a utility path arrives without reuse-decision.yaml | The team |
A banned-pattern rule in a grep or semgrep style (illustrative):
rules:
- id: date-only-parsed-as-instant
languages: [typescript, javascript]
severity: ERROR
message: >
dueDate is a date-only value. new Date() turns it into an instant at
midnight and can show the wrong calendar day. Use LocalDate.parse from
shared-datetime (temporal-domain contract v1).
pattern-either:
- pattern: new Date($X.dueDate)
- pattern: new Date(dueDate)
- pattern: Date.parse($X.dueDate)
- id: fixed-utc-offset
languages: [typescript, javascript, java]
severity: ERROR
message: Use an IANA zone name from profile-service, never a fixed offset.
pattern-regex: '"[+-]0[0-9]:00"'
A decision-record check (illustrative):
# ci/check-reuse-record.sh: fail when a new utility file arrives without a record
new_utils=$(git diff --name-only --diff-filter=A origin/main...HEAD -- 'src/**/util*' 'src/**/*date*')
if [ -n "$new_utils" ] && [ ! -f reuse-decision.yaml ]; then
echo "New utility files without reuse-decision.yaml:"; echo "$new_utils"
exit 1
fi
Branch protection makes these checks required and adds a required reviewer who is not the author. When a coding agent opened the pull request, the person who asked the agent is the author for this rule.
9. Layers 7 and 8 · exceptions and catalog feedback
| Item | Exception | Catalog feedback |
|---|---|---|
| Who raises it | The developer, with the asset owner | Anyone; the hook and CI log events automatically |
| Required fields | Owner, reason, expiry, compensating control | Kind (false positive, missing asset, stale entry), evidence |
| Example | Dispute service keeps its parser for one quarter (illustrative); compensating control: input validation tests | The hook flagged a test fixture 14 times in a month (illustrative); scope narrowed |
| What happens at expiry | The CI check reads the register; an expired exception fails the build | Not applicable |
| Reviewed by | Architecture; second line for customer-facing assets | Architecture, monthly |
10. The asset lifecycle
A reusable asset has a life: someone proposes it, someone assesses it, and someone retires it. Without the last step, the catalog fills with entries that agents still recommend.
| Step | Who acts | Output | shared-datetime example |
|---|---|---|---|
| Propose | Any team | Catalog request with the capability and first consumer | Temporal working group proposes the contract and library |
| Assess | Architecture, security, legal | Assurance level, licence, owner, funding | Security review, licence check, owner named |
| Publish | Asset owner | Catalog entry, examples, contract link | Version 4.2 published with the contract |
| Discover | Developers and coding agents | Catalog queries, hook candidates | The hook returns it for a new date helper |
| Reuse or extend | Consuming team, with the owner | Decision record; extension pull request to the asset | formatPaymentCutoff (display only) added upstream, not copied |
| Monitor | Asset owner | Adoption, defects, version spread, false positives | 3.x still in four repositories (illustrative) |
| Deprecate | Asset owner, architecture | End-of-support date; catalog status changes; CI warns, then fails | 3.x marked deprecated; the hook stops recommending it |
Next: the worked example applies every layer to one ask. Copy-ready versions of the hook specification, the decision record and the catalog entry are in templates; Lab 3 in labs builds the hook and the CI check.