notion-policy-guardrails
Governance for Notion integrations: integration naming standards, page sharing policies, property naming conventions, database schema standards, and access audit scripts. Trigger with phrases like "notion governance", "notion policy", "notion naming convention", "notion access audit", "notion schema standard".
What this skill does
# Notion Policy & Guardrails
## Overview
Governance framework for Notion integrations at scale. Covers integration naming standards for consistent bot identification, page sharing policy enforcement to prevent accidental data exposure, property naming conventions for cross-team database consistency, database schema validation standards, and access audit scripts that scan which integrations have access to which pages. Uses `Client` from `@notionhq/client` for programmatic enforcement.
## Prerequisites
- `@notionhq/client` v2.x installed (`npm install @notionhq/client`)
- Python: `notion-client` installed (`pip install notion-client`)
- `NOTION_TOKEN` environment variable set (admin-level integration recommended for audits)
- CI/CD pipeline (GitHub Actions examples provided)
## Instructions
### Step 1: Integration Naming Standards and Token Management
Establish naming conventions for integrations so teams can identify which bot accessed what.
```typescript
import { Client } from '@notionhq/client';
// Naming convention: {team}-{env}-{purpose}
// Examples: eng-prod-sync, marketing-staging-cms, data-prod-etl
interface IntegrationConfig {
name: string; // Must match: /^[a-z]+-[a-z]+-[a-z]+$/
token: string;
environment: 'dev' | 'staging' | 'prod';
owner: string; // Team or individual
capabilities: string[]; // What it's allowed to do
}
function validateIntegrationName(name: string): string[] {
const issues: string[] = [];
const pattern = /^[a-z]+-[a-z]+-[a-z]+$/;
if (!pattern.test(name)) {
issues.push(`Name "${name}" must match pattern: {team}-{env}-{purpose} (e.g., eng-prod-sync)`);
}
const [team, env] = name.split('-');
const validEnvs = ['dev', 'staging', 'prod'];
if (env && !validEnvs.includes(env)) {
issues.push(`Environment "${env}" must be one of: ${validEnvs.join(', ')}`);
}
return issues;
}
// Validate at startup — fail fast if misconfigured
async function validateIntegration(notion: Client, config: IntegrationConfig): Promise<void> {
const nameIssues = validateIntegrationName(config.name);
if (nameIssues.length > 0) {
throw new Error(`Integration naming violation:\n${nameIssues.join('\n')}`);
}
// Verify the token works and matches expected bot name
const me = await notion.users.me({});
if (me.type !== 'bot') {
throw new Error('Token is not a bot integration token');
}
console.log(`Integration validated: ${config.name} (bot: ${me.name})`);
}
// Token rotation tracking
interface TokenRegistry {
integrations: Array<{
name: string;
tokenPrefix: string; // First 8 chars for identification
createdDate: string;
rotateBy: string; // Max 90 days
owner: string;
}>;
}
function checkTokenExpiry(registry: TokenRegistry): string[] {
const warnings: string[] = [];
const now = new Date();
for (const integration of registry.integrations) {
const rotateBy = new Date(integration.rotateBy);
const daysUntilExpiry = Math.ceil((rotateBy.getTime() - now.getTime()) / 86400000);
if (daysUntilExpiry < 0) {
warnings.push(`EXPIRED: ${integration.name} — rotate immediately (expired ${-daysUntilExpiry} days ago)`);
} else if (daysUntilExpiry < 14) {
warnings.push(`WARNING: ${integration.name} — expires in ${daysUntilExpiry} days`);
}
}
return warnings;
}
```
### Step 2: Page Sharing Policies and Property Naming Conventions
Enforce which pages can be shared with integrations and standardize property names across databases.
```typescript
// Property naming conventions — enforced via database schema validation
const PROPERTY_NAMING_RULES = {
// Titles always PascalCase
title: /^[A-Z][a-zA-Z]+(\s[A-Z][a-zA-Z]+)*$/,
// Status properties use specific names
allowedStatusNames: ['Status', 'Stage', 'State'],
// Date properties end with "Date" or "At"
date: /Date$|At$/,
// Relation properties end with target
relation: /^Related\s|^Parent\s/,
// Multi-select use plural names
multi_select: /s$/,
// Banned property names (too generic)
banned: ['Data', 'Info', 'Stuff', 'Other', 'Misc', 'Notes2'],
};
async function auditDatabaseSchema(
notion: Client,
databaseId: string
): Promise<{ violations: string[]; recommendations: string[] }> {
const db = await notion.databases.retrieve({ database_id: databaseId });
const violations: string[] = [];
const recommendations: string[] = [];
for (const [name, prop] of Object.entries(db.properties)) {
// Check banned names
if (PROPERTY_NAMING_RULES.banned.includes(name)) {
violations.push(`"${name}": banned property name — use a descriptive name`);
}
// Check title format
if (!PROPERTY_NAMING_RULES.title.test(name)) {
recommendations.push(`"${name}": consider PascalCase (e.g., "${name.replace(/\b\w/g, c => c.toUpperCase())}")`);
}
// Check date naming
if (prop.type === 'date' && !PROPERTY_NAMING_RULES.date.test(name)) {
recommendations.push(`"${name}": date properties should end with "Date" or "At" (e.g., "${name} Date")`);
}
// Check multi-select naming
if (prop.type === 'multi_select' && !PROPERTY_NAMING_RULES.multi_select.test(name)) {
recommendations.push(`"${name}": multi-select properties should use plural names (e.g., "${name}s")`);
}
}
// Check for required properties
const propTypes = Object.entries(db.properties).map(([name, prop]) => ({ name, type: prop.type }));
const hasTitle = propTypes.some(p => p.type === 'title');
if (!hasTitle) {
violations.push('Database must have a title property');
}
return { violations, recommendations };
}
// Page sharing policy: audit which pages are accessible
async function auditPageAccess(
notion: Client,
databaseId: string
): Promise<void> {
console.log('=== Page Access Audit ===');
const response = await notion.databases.query({
database_id: databaseId,
page_size: 100,
});
for (const page of response.results) {
if (!('parent' in page)) continue;
const pageObj = page as any;
// Check if page has public URL (shared publicly)
if (pageObj.public_url) {
console.warn(`PUBLIC PAGE: ${page.id} — ${pageObj.public_url}`);
}
// Log page access for audit trail
console.log(`Page ${page.id}: parent=${pageObj.parent.type}, created=${pageObj.created_time}`);
}
}
```
```python
from notion_client import Client
notion = Client(auth=os.environ["NOTION_TOKEN"])
def audit_database_schema(database_id: str) -> dict:
"""Validate database schema against naming conventions."""
db = notion.databases.retrieve(database_id=database_id)
violations = []
recommendations = []
banned_names = {"Data", "Info", "Stuff", "Other", "Misc"}
for name, prop in db["properties"].items():
if name in banned_names:
violations.append(f'"{name}": banned property name')
if prop["type"] == "date" and not (name.endswith("Date") or name.endswith("At")):
recommendations.append(f'"{name}": date properties should end with Date/At')
if prop["type"] == "multi_select" and not name.endswith("s"):
recommendations.append(f'"{name}": multi-select should use plural name')
return {"violations": violations, "recommendations": recommendations}
```
### Step 3: Access Audit Scripts and Database Schema Standards
Comprehensive audit of what integrations can access across your workspace, plus CI-enforced schema validation.
```typescript
// Full workspace access audit
async function workspaceAccessAudit(notion: Client): Promise<void> {
console.log('=== Workspace Access Audit ===');
console.log(`Timestamp: ${new Date().toISOString()}`);
// 1. Identify the integration
const me = await notion.users.me({});
console.log(`\nIntegration: ${me.name} (${me.id})`);
// 2. Search all accessible content
const allContent: Array<{ type: string; id: string; title: string }> = [];
let cursor: string | undefined;
do {Related in Security
mac-ops
IncludedComprehensive macOS workstation operations — diagnose kernel panics, identify failing drives, audit launchd startup items, decode wake reasons, triage TCC permission denials, manage APFS snapshots, recover from no-boot. Use for: Mac is slow, slow bootup, won't boot, kernel panic, kernel_task hot, mds_stores CPU, photoanalysisd, cloudd, login loop, gray screen, sleep wake failure, drive failing, IO errors, APFS snapshots eating space, Time Machine local snapshots, Spotlight indexing, launchd, LaunchAgent, LaunchDaemon, login items, TCC permissions, Full Disk Access, Screen Recording denied, Gatekeeper, quarantine, com.apple.quarantine, app is damaged, helper tool, /Library/PrivilegedHelperTools, pmset, wake reasons, dark wake, sysdiagnose, panic.ips, DiagnosticReports, configuration profile, MDM profile, remote diagnostics over SSH.
a11y-audit
IncludedRun accessibility audits on web projects combining automated scanning (axe-core, Lighthouse) with WCAG 2.1 AA compliance mapping, manual check guidance, and structured reporting. Output is configurable: markdown report only, markdown plus machine-readable JSON, or markdown plus issue tracker integration. Use this skill whenever the user mentions "accessibility audit", "a11y audit", "WCAG audit", "accessibility check", "compliance scan", or asks to check a web project for accessibility issues. Also trigger when the user wants to verify WCAG conformance or map findings to a specific standard (CAN-ASC-6.2, EN 301 549, ADA/AODA).
erpclaw
IncludedAI-native ERP system with self-extending OS. Full accounting, invoicing, inventory, purchasing, tax, billing, HR, payroll, advanced accounting (ASC 606/842, intercompany, consolidation), and financial reporting. 413 actions across 14 domains, 43 expansion modules. Constitutional guardrails, adversarial audit, schema migration. Double-entry GL, immutable audit trail, US GAAP.
assess
IncludedAssesses and rates quality 0-10 across multiple dimensions (correctness, maintainability, security, performance, testability, simplicity) with pros/cons analysis. Compares against project conventions and prior decisions from memory. Produces structured evaluation reports with actionable improvement suggestions. Use when evaluating code, designs, architectures, or comparing alternative approaches.
spring-boot-security-jwt
IncludedProvides JWT authentication and authorization patterns for Spring Boot 3.5.x covering token generation with JJWT, Bearer/cookie authentication, database/OAuth2 integration, and RBAC/permission-based access control using Spring Security 6.x. Use when implementing authentication or authorization in Spring Boot applications.
code-hardcode-audit
IncludedDetect hardcoded values, magic numbers, and leaked secrets. TRIGGERS - hardcode audit, magic numbers, PLR2004, secret scanning.