A requirement and an issue are not the same object. The requirement is owned by the business owner, states the question and the acceptance test, and lives as long as the report does. Issues are owned by engineers, close, and there are several per requirement. Collapsing them into one ticket is what produces a closed ticket describing a table nobody can justify.
Intake — seven fields complete. The requirement does not exist until they are.
Feasible — five questions run, the failing one recorded with its evidence, three options presented, one chosen and signed.
Specified — the acceptance test runs. Returning the wrong number is fine at this stage; failing to execute is not.
In build — one or more issues, each carrying the requirement ID in the branch name and the PR title.
Accepted — the acceptance test runs green with the requester watching. Not when the PR merges.
Live — object tagged, owner group set, runbook entry exists.
One requirement usually lands on several objects, and one dashboard usually rests on several requirements: REQ-118 arrives as segment and country dimensions on gold.mv_sales, while the monthly pre-aggregate under the same dashboard is its own requirement with its own owner. That is normal, and it is why the link is carried by the object rather than by the report.
Governed tags carry the warehouse end. They are GA, defined once at account level, and deployed in DDL rather than clicked into a UI:
Why
Month fourteen, somebody asks why gold.agg_revenue_month excludes cancelled orders. Without the link the answer is archaeology: git blame, then a chat search, then a person who has left. With it, the tag names REQ-121, and REQ-121 names the rule, the owner, the acceptance number and the date it was agreed.
The reverse direction matters as often. When the owner asks to change the definition of revenue, you need every object that implements rule 1 before you can quote a cost — the tag sweep lists the candidates in seconds, and lineage then tells you what reads them.
How
sqlThe warehouse end of the link — deployed, reviewed, versioned
ALTER TABLE gold.agg_revenue_month
SET TAGS ('requirement' = 'REQ-121', 'owner' = 'grp_dwh_gold');
COMMENT ON TABLE gold.agg_revenue_month IS
'One row per month x region_sk x product group. Revenue excludes
status = CANCELLED (rule 1). Requirement REQ-121. Owner grp_dwh_gold.
Loaded daily 07:30 CET from gold.fct_order_line, after the 07:00 gold load.';
Which is worth doing because of what it makes queryable — the objects nobody can name a reason for:
sqlOrphan sweep — run monthly, in the team's own review
-- No table_type predicate, deliberately. information_schema.tables reports
-- MANAGED, EXTERNAL, VIEW, MATERIALIZED_VIEW and STREAMING_TABLE, and the
-- objects a report actually reads -- gold.mv_sales, the materialized views
-- under a dashboard -- are NOT of type MANAGED. Filtering to MANAGED makes
-- exactly those objects invisible to the sweep that exists to find them.
-- Report the type instead and let the reviewer see what they are looking at.
SELECT t.table_name,
t.table_type
FROM dwh_prod.information_schema.tables t
LEFT JOIN dwh_prod.information_schema.table_tags g
ON g.schema_name = t.table_schema
AND g.table_name = t.table_name
AND g.tag_name = 'requirement'
WHERE t.table_schema = 'gold'
AND g.tag_name IS NULL
ORDER BY t.table_type, t.table_name;
Note the missing table_type filter. The semantic layer is the whole argument for defining revenue once in the warehouse, which means a metric view or a materialized view is the object a dashboard reads and therefore the object whose requirement link matters most — and it is not a MANAGED table. A sweep narrowed to managed tables comes back clean while gold.mv_sales sits untagged, which is worse than not sweeping: it certifies an object it never looked at.
It returns two kinds of row: objects built before the process existed, and objects built around it last week. Both deserve a conversation. The second deserves it immediately, and it is much easier to have when the query found it rather than when you did.
Doing it
Put the requirement ID in the branch name, the PR title and the table tagOne string, three places, all greppable.
Set tags and comments in deployed DDLA tag clicked into Catalog Explorer survives only until the pipeline next recreates the table.
Close issues on merge and requirements on acceptance — never the other way round, and never both at once
Tag the object the report reads, not only the table underneath itThe requirement link on gold.fct_order_line does not answer 'why does this dashboard exist' — the one on gold.mv_sales does.
Run the orphan sweep monthly, into the team's review rather than your status reportRead the table_type column while you are there: an untagged MATERIALIZED_VIEW or STREAMING_TABLE is the row that matters, because something is scheduled behind it.
Embedding it
Let the team run the orphan sweep in front of the whole group the first timeTheir own untagged tables make the argument better than you can.
Have them send the acceptance invitation and run the demoIf you drive the sign-off meeting, the requester's relationship is with you and it ends when you do.
Put the tag on the review checklist so it is caught while the DDL is still open and the change is free
At handover, pick a gold table at random and ask a team member to trace it to its requirementTwo minutes means the link is real; ten minutes of searching means it is decoration.
Defaults
Requirement and issue are separate objects with separate owners and separate lifetimes
Accepted means the acceptance test ran green with the requester in the room
The link lives in the warehouse as a governed tag, not only in the tracker
Rejected and cancelled requirements are keptThey are the record of what was already asked and answered.
The orphan sweep covers every object type in goldViews, materialized views, streaming tables and metric views included — because those are what the reports actually read.
The orphan sweep is a standing monthly item owned by the team
Gotchas
Closing the requirement when the PR merges'Done' then means 'deployed', and the first person to notice the number is wrong is the person who asked for it — in a meeting, with an audience.
The requirement living in a chat threadIn eighteen months the reason revenue excludes cancelled orders sits in a channel of a tool the client has since replaced, and the rule gets re-decided differently.
One epic per report with forty issues under itWhen a rule turns out to be wrong you cannot tell which change implemented it, so the investigation is a re-read of everything and the estimate for a one-line fix is a week.
Tags applied by hand in Catalog Explorer, or a tagged table renamed without touching the requirementBoth fail the same way: nothing errors, the link simply stops existing, and the orphan sweep is the only thing that notices.
Scoping the orphan sweep with t.table_type = 'MANAGED'information_schema.tables distinguishes MANAGED from VIEW, MATERIALIZED_VIEW, STREAMING_TABLE and EXTERNAL, and the semantic-layer objects a dashboard reads — gold.mv_sales and the materialized views around it — are precisely the ones that predicate excludes. The sweep then returns an empty result and gets reported as 'everything is tagged', while the untagged object the report depends on has never once been examined. A control that cannot see the class of object it was built for is worse than no control, because it produces a green answer.
Deploying DDL that sets a governed tag key nobody has created yetGoverned tags are defined once at account level and assigned by whoever holds that permission — not by the engineer writing the ALTER TABLE — so the release fails on the tag rather than on the table, and unblocking it needs a person who is not in the sprint. Create the keys with the catalogs, before the first table needs one.